題目
給定一個 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