每日leetcode——二分查詢

Ethan發表於2022-02-22

題目

給定一個 n 個元素有序的(升序)整型陣列 nums 和一個目標值 target ,寫一個函式搜尋 nums 中的 target,如果目標值存在返回下標,否則返回 -1。

輸入: nums = [-1,0,3,5,9,12], target = 9
輸出: 4
解釋: 9 出現在 nums 中並且下標為 4

暴力列舉

for迴圈遍歷陣列元素,挨個判斷。時間複雜度O(n)

def search(nums, target) -> int:
    for i,num in enumerate(nums):
        if num == target: return i
    return -1

二分法

二分法,適用於這種有序陣列的查詢。
二分法的思想,就是每次取中間的值與target比較,然後縮小範圍再取中間的值...:

  • 如果中間值<target,就收縮left
  • 如果中間值>target,就收縮right
  • 如果中間值=target,需要分情況討論

    • 如果陣列是[1,2,3,4]這種有序且不重複,就直接找到了
    • 如果陣列是其他情況,比如有重複,部分有序,部分有序且有重複,就需要考慮左右邊界,因為陣列中可能有多個等於target的數,需要找最左側的或是最右側的

二分法時間複雜度O(logn),n/2^k=1,k=logn

標準二分,陣列有序無重複元素

[1,2,3,4,5],陣列有序且無重複元素
while迴圈實現

def search(nums, target) -> int:
    left,right = 0, len(nums)-1
    while left <= right:
        mid = (left+right)//2
        if nums[mid]==target:
            return mid
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

遞迴實現

def search(nums,target) -> int:
    def searchInternally(nums,target,left,right):
        if left<=right:
            mid = (left+right)//2
            if nums[mid]==target: 
                return mid
            elif nums[mid]<target:
                return searchInternally(nums,target,mid+1,right)
            else:
                return searchInternally(nums,target,left,mid-1)
        else:
            return -1
    return searchInternally(nums,target,0,len(nums)-1)

考慮邊界

陣列有重複元素:[1,2,2,2,3]
陣列部分有序:[4,5,6,1,2,3]

# 查詢左邊界
def search(nums,target):
    left,right = 0, len(nums)-1
    while left<right:
        mid = (left+right)//2
        # 因為有重複元素,並且尋找左邊界,所以當匹配到target後,收縮right,繼續向左查詢
        if nums[mid]==target:
            right = mid
        if nums[mid] > target:
            right = mid -1
        if nums[mid] < target:
            left = mid +1
    return left if nums[left]==target else -1

# 查詢右邊界
def search(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        # 因為查詢右邊界,mid原本的計算是向下取整,導致靠左,所以+1靠右
        mid = (left + right) // 2 + 1
        if nums[mid] == target:
            # 收縮left,繼續向右查詢
            left = mid
        if nums[mid] > target:
            right = mid - 1
        if nums[mid] < target:
            left = mid + 1
    return right if nums[right] == target else -1

陣列部分有序,且重複:[1,2,2,3,1,2,2,3]

# 查詢左邊界
def search(nums, target):
    left,right = 0, len(nums)-1
    while left < right:
        mid = (left+right)//2
        if nums[mid]==target:
            right = mid
        if nums[mid]>target:
            # 因為陣列部分有序且重複,mid大於target時,
            # 有可能mid左側沒有目標值,在右側有,因此收縮right時只能一點一點收縮
            right = right - 1
        if nums[mid]<target:
            left = mid + 1
    return left if nums[left]==target else -1

二分法中間值溢位

(left+right)//2雖然能計算出中間值,但是這種簡單的計算方式可能會存在整數溢位的可能。
雖然,在python3中,int不會溢位。
比如,假設當前整數的最大範圍是20,如果left=10,right=20,此時left+right已經超過最大範圍,就會溢位。
更科學的計算方法是:(right-left)//2+left

相關文章