二分查找细节
第一个,最基本的二分查找算法:
1 因为我们初始化 right = nums.length - 1
2 所以决定了我们的「搜索区间」是 [left, right]
3 所以决定了 while (left <= right)
4 同时也决定了 left = mid+1 和 right = mid-1
5
6 因为我们只需找到一个 target 的索引即可
7 所以当 nums[mid] == target 时可以立即返回
第二个,寻找左侧边界的二分查找:
1 因为我们初始化 right = nums.length
2 所以决定了我们的「搜索区间」是 [left, right)
3 所以决定了 while (left < right)
4 同时也决定了 left = mid + 1 和 right = mid
5
6 因为我们需找到 target 的最左侧索引
7 所以当 nums[mid] == target 时不要立即返回
8 而要收紧右侧边界以锁定左侧边界
第三个,寻找右侧边界的二分查找:
1 因为我们初始化 right = nums.length
2 所以决定了我们的「搜索区间」是 [left, right)
3 所以决定了 while (left < right)
4 同时也决定了 left = mid + 1 和 right = mid
5
6 因为我们需找到 target 的最右侧索引
7 所以当 nums[mid] == target 时不要立即返回
8 而要收紧左侧边界以锁定右侧边界
9
10 又因为收紧左侧边界时必须 left = mid + 1
11 所以最后无论返回 left 还是 right,必须减一
刷题
-
- ⼆分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-1
ans = -1
while left <= right:
mid = (left+right)//2
if nums[mid] == target:
ans = mid
left = mid + 1
return ans
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return ans
-
- 搜索插⼊位置
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
else:
return middle
return right + 1
-
- 猜数字⼤⼩
class Solution:
def guessNumber(self, n: int) -> int:
left, right = 1, n
while left < right:
mid = (left + right) // 2
if guess(mid) <= 0:
right = mid # 答案在区间 [left, mid] 中
else:
left = mid + 1 # 答案在区间 [mid+1, right] 中
# 此时有 left == right,区间缩为一个点,即为答案
return left
-
- Sqrt(x)
注意返回左边界值
- Sqrt(x)
class Solution:
def mySqrt(self, x: int) -> int:
left = 0
right = x
ans = -1
while left <= right :
mid = (left + right)//2
if mid * mid <= x:
left += 1
ans = mid
else:
right = mid -1
return ans
-
- 两数之和 II - 输⼊有序数组
双指针,时间复杂度O(n)
- 两数之和 II - 输⼊有序数组
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left = 0
right = len(numbers) - 1
while left < right:
if numbers[left] + numbers[right] == target:
return left+1, right+1
if numbers[left] + numbers[right] > target:
right -= 1
if numbers[left] + numbers[right] < target:
left += 1
-
- 在 D 天内送达包裹的能⼒
class Solution:
def shipWithinDays(self, weights: List[int], D: int) -> int:
# 最小值得是任何一个货物都可以运走, 不可以分割货物
start = max(weights)
# 最大值是一趟就全部运走, 所以是所有货物之和
end = sum(weights)
# 二分法模板
while start < end:
# 先求中间值
mid = (start + end)//2
# 计算这个中间值需要计算需要多少天运完
days = self.countDays(mid, weights)
# 如果天数超了, 说明运载能力有待提升, start改大一点, 继续二分搜索
if days > D:
start = mid + 1
# 否则运载能力改小一点继续搜索
else:
end = mid
return start
def countDays(self, targetWeight, weights):
days = 1
current = 0
for weight in weights:
current += weight
if current > targetWeight:
days += 1
#当满足运载量,则运输一批,天数增加1,接着从超重货物开始计重
current = weight
return days
- 0278 第一个错误的版本
左闭右开区间
class Solution:
def firstBadVersion(self, n):
left = 1
right = n
while left < right:
mid = (left + right) // 2
if isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
- 0033 搜索旋转排序数组
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
#确认target在旋转数组的哪一侧
if nums[mid] >= nums[left]:
#确认target所在区间,注意区分等号
if nums[mid] > target and target >= nums[left]:
right = mid - 1
else:
left = mid + 1
else:
if nums[mid] < target and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
- 0153 寻找旋转排序数组中的最小值
class Solution:
def findMin(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while left < right :
mid = ( left + right )//2
#mid 与左右极值对比
if nums[mid] > nums[right]:
left = mid +1
else:
right = mid
return nums[left]
参考资料:
算法通关手册 https://github.com/itcharge/LeetCode-Py
https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/solution/chi-xiao-dou-mei-ri-yi-ti-python-jian-mi-o01o/