https://labuladong.online/algo/frequency-interview/binary-search-in-action/ 和https://labuladong.online/algo/problem-set/binary-search/
1. 二分搜索的框架
# 函数 f 是关于自变量 x 的单调函数
def f(x: int) -> int:
# ...
# 主函数,在 f(x) == target 的约束下求 x 的最值
def solution(nums: List[int], target: int) -> int:
if len(nums) == 0:
return -1
# 问自己:自变量 x 的最小值是多少?
left = ...
# 问自己:自变量 x 的最大值是多少?
right = ... + 1
while left < right:
mid = left + (right - left) // 2
if f(mid) == target:
# 问自己:题目是求左边界还是右边界?
# ...
elif f(mid) < target:
# 问自己:怎么让 f(x) 大一点?
# ...
elif f(mid) > target:
# 问自己:怎么让 f(x) 小一点?
# ...
return left
2. 吃香蕉 & 运货物问题
2.1 coco吃香蕉
https://leetcode.cn/problems/koko-eating-bananas/
f(k): 吃香蕉的速度为k,返回多长时间能吃完。寻找f(k) == h的左侧边界
注意f(k)对k是递减的,二分搜索的时候lo hi的增减要相应变化
class Solution(object):
def minEatingSpeed(self, piles, h):
"""
:type piles: List[int]
:type h: int
:rtype: int
"""
def time(piles, k):
res = 0
for p in piles:
res += p//k
if p%k != 0:
res += 1
return res
lo, hi = 1, max(piles)
while lo <= hi:
mid = lo + (hi-lo)//2
if time(piles, mid) > h:
lo = mid + 1
else:
hi = mid - 1
return lo
2.2 运送货物问题
https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/
和吃香蕉问题逻辑基本一样,区别在于一艘船可以装多个货物,time函数要稍作处理。相当于吃完这堆香蕉后这个小时内还会继续吃其他的香蕉。
class Solution(object):
def shipWithinDays(self, weights, days):
"""
:type weights: List[int]
:type days: int
:rtype: int
"""
def time(piles, k):
res = 1
temp = k
for p in piles:
if temp >= p:
temp -= p
else:
temp = k - p
res += 1
return res
lo, hi = max(weights), sum(weights)
while lo <= hi:
mid = lo + (hi-lo)//2
if time(weights, mid) > days:
lo = mid + 1
else:
hi = mid - 1
return lo
2.3 分割数组的最大值
这道题,看起来比较绕,其实是跟2一模一样的,只是换了个皮。
- 原题:给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。设计一个算法使得这 k 个子数组各自和的最大值最小。
- 改造:你只有一艘货船,现在有若干货物,每个货物的重量是 nums[i],现在你需要在 m 天内将这些货物运走,请问你的货船的最小载重是多少?
class Solution(object):
def splitArray(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
def helper(weights, x):
d = 1
temp = x
for w in weights:
if temp >= w:
temp -= w
else:
temp = x - w
d += 1
return d
lo, hi = max(nums), sum(nums)
while lo <= hi:
mid = lo + (hi-lo)//2
if helper(nums, mid) > k:
lo = mid + 1
else:
hi = mid - 1
return lo
3. 二分搜索应用:搜索二维矩阵
3.1 搜索二维矩阵1
https://leetcode.cn/problems/search-a-2d-matrix/description/
思路1:二维转一维,把m x n矩阵转化为长度为m x n的一维数组,用一次二分搜索。
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
def get(matrix, i):
row = i // len(matrix[0])
col = i % len(matrix[0])
print(i, row, col)
return matrix[row][col]
lo, hi = 0, len(matrix) * len(matrix[0]) - 1
while lo <= hi:
mid = lo + (hi-lo)//2
if get(matrix, mid) == target:
return True
elif get(matrix, mid) > target:
hi = mid - 1
else:
lo = mid + 1
return False
思路2:对列和行分别做两次二分搜索
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
if not matrix:
return False
m, n = len(matrix), len(matrix[0])
lo, hi = 0, m-1
while lo <= hi:
mid = lo + (hi-lo)//2
if matrix[mid][0] == target:
return True
elif matrix[mid][0] > target:
hi = mid - 1
else:
lo = mid + 1
if hi < 0:
return False
else:
row = hi
lo, hi = 0, n-1
while lo <= hi:
mid = lo + (hi - lo)//2
if matrix[row][mid] == target:
return True
elif matrix[row][mid] > target:
hi = mid - 1
else:
lo = mid + 1
return False
3.2 搜索二维矩阵2
https://leetcode.cn/problems/search-a-2d-matrix-ii/description/
Z字形查找,从右上角开始搜索,如果> target就往左,否则往下。
时间复杂度O(m+n)
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[int]]
:type target: int
:rtype: bool
"""
m, n = 0, len(matrix[0]) - 1
while m < len(matrix) and n >= 0:
if matrix[m][n] > target:
n -= 1
elif matrix[m][n] < target:
m += 1
else:
return True
return False
4. 找到k个最接近的元素
https://leetcode.cn/problems/find-k-closest-elements/description/
先搜索左侧边界算法找出leftbound;
再left = lo-1, right = lo
然后向外扩散,双指针求解
class Solution(object):
def findClosestElements(self, arr, k, x):
"""
:type arr: List[int]
:type k: int
:type x: int
:rtype: List[int]
"""
lo, hi = 0, len(arr)-1
while lo <= hi:
mid = lo + (hi-lo)//2
if arr[mid] < x:
lo = mid + 1
else:
hi = mid - 1
res = []
left, right = lo - 1, lo
while right - left - 1 < k:
if left < 0:
res.append(arr[right])
right += 1
elif right > len(arr)-1:
res.insert(0, arr[left])
left -= 1
elif x - arr[left] > arr[right] - x:
res.append(arr[right])
right += 1
else:
res.insert(0, arr[left])
left -= 1
return res
5. 峰值问题
5.1 寻找峰值
https://leetcode.cn/problems/find-peak-element/
循环条件:lo < hi,而不是lo <= hi
判断条件: nums[mid] > nums[mid+1]
class Solution(object):
def findPeakElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
lo, hi = 0, len(nums)-1
while lo < hi:
mid = lo + (hi-lo)//2
if nums[mid] > nums[mid+1]:
hi = mid
else:
lo = mid + 1
return lo
5.2 山脉数组的峰顶索引
https://leetcode.cn/problems/peak-index-in-a-mountain-array/description/
和5.1基本一致
class Solution(object):
def peakIndexInMountainArray(self, arr):
"""
:type arr: List[int]
:rtype: int
"""
lo, hi = 0, len(arr)-1
while lo < hi:
mid = lo + (hi-lo)//2
if arr[mid] > arr[mid+1]:
hi = mid
else:
lo = mid + 1
return lo
5.3 0到n-1中缺失的数字
leetcode:点名
https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/
注意点:判断条件records[mid] > mid
class Solution(object):
def takeAttendance(self, records):
"""
:type records: List[int]
:rtype: int
"""
lo, hi = 0, len(records)-1
while lo <= hi:
mid = lo + (hi-lo)//2
if records[mid] > mid:
hi = mid - 1
else:
lo = mid + 1
return lo
6 搜索旋转排序数组
6.1 元素不可重复
https://leetcode.cn/problems/search-in-rotated-sorted-array/
先判断nums[mid]在左边还是右边,再判断target和nums[mid]的大小关系
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
if not nums:
return -1
lo, hi = 0, len(nums)-1
while lo <= hi:
mid = lo + (hi-lo)//2
if nums[mid] == target:
return mid
if nums[mid] >= nums[lo]:
if target >= nums[lo] and target < nums[mid] :
hi = mid - 1
else:
lo = mid + 1
else:
if target > nums[mid] and target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return -1
6.2 元素可重复
https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/
思路类似,也是先判断nums[mid]是在左边还是右边。但因为元素可重复,当nums[mid] == nums[lo] == nums[hi]的时候无法判断。因此对lo和hi区间进行缩减,不要出现相等的情况。
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: bool
"""
if not nums:
return -1
lo, hi = 0, len(nums)-1
while lo <= hi:
while lo < hi and nums[lo] == nums[lo+1]:
lo += 1
while lo < hi and nums[hi] == nums[hi-1]:
hi -= 1
mid = lo + (hi-lo)//2
if nums[mid] == target:
return True
if nums[mid] >= nums[lo]:
if target >= nums[lo] and target < nums[mid] :
hi = mid - 1
else:
lo = mid + 1
else:
if target > nums[mid] and target <= nums[hi]:
lo = mid + 1
else:
hi = mid - 1
return False
1347





