五、二分法
1. 二分迭代程序(找到的位置可能不是第一次出现的位置)
def bi_search_iter(alist, item):
left, right = 0, len(alist) - 1
while left <= right:
mid = (left + right) // 2
if alist[mid] < item:
left = mid + 1
elif alist[mid] > item:
right = mid - 1
else:
return mid
return -1
2. 二分推荐模板(能确保找到的元素是第一次出现的位置)
def binarysearch(alist, item):
if len(alist) == 0:
return -1
left, right = 0, len(alist) - 1
while left + 1 < right: # (1) left == right (2) left + 1 == right,即左指针和右指针相邻
mid = left + (right - left) // 2
if alist[mid] == item:
right = mid # 确保找到的是第一个元素 如在[1, 1, 2, 2, 2, 2,3, 4] 里面找2的出现的位置,这句话就能保证找到第一个2出现的位置。
elif alist[mid] < item:
left = mid
elif alist[mid] > item:
right = mid
if alist[left] == item: # 当最后的左指针和右指针相邻时,有可能left和right所指的元素都为2,此时left指针所指的位置为答案
return left
if alist[right] == item:
return right
return -1
3. leetcode第153题. 寻找旋转排序数组中的最小值
# O(nlogn)
def searchlazy(alist):
alist.sort()
return alist[0]
# O(n)
def searchslow(alist):
mmin = alist[0]
for i in alist:
mmin = min(mmin, i)
return mmin
# O(lgn)
def search(alist):
if len(alist) == 0:
return -1
left, right = 0, len(alist) - 1
while left + 1 < right:
if (alist[left] < alist[right]): # 排好序的情况
return alist[left]
mid = left + (right - left) // 2
if (alist[mid] >= alist[left]): # 拐点出现在后半部分 如 [3,4,5,1,2]
left = mid + 1
else: # 拐点出现在前半部分 9如[7,8,9,1,2,3,4,5,6]
right = mid
return alist[left] if alist[left] < alist[right] else alist[right]
4. leetcode第33题. 搜索旋转排序数组
def search(alist, target):
if len(alist) == 0:
return -1
left, right = 0, len(alist) - 1
while left + 1 < right:
mid = left + (right - left) // 2
if alist[mid] == target:
return mid
if (alist[left] < alist[mid]): # 拐点出现在前半部分 如 [3,4,5,1,2] 说明前半部分是排好序的 [1,3,5]
if alist[left] <= target and target <= alist[mid]: # 如果目标值出现在此排好序的范围内,我就在这里面查找
right = mid
else: # 否则我去后面没有排好序的查找
left = mid
else: # 拐点出现在后半部分 如[7,8,9,1,2,3,4,5,6],说明后半部分是排好序的
if alist[mid] <= target and target <= alist[right]:
left = mid
else:
right = mid
if alist[left] == target:
return left
if alist[right] == target:
return right
return -1
5. leetcode第35题. 搜索插入位置
def search_insert_position(alist, target):
if len(alist) == 0:
return 0
left, right = 0, len(alist) - 1
while left + 1 < right:
mid = left + (right - left) // 2
if alist[mid] == target:
return mid
if (alist[mid] < target):
left = mid
else:
right = mid
if alist[left] >= target: # 如【1,3,5,6】插入0或1
return left
if alist[right] >= target: # 如【1,3,5,6】插入2,4
return right
return right + 1 # 如[1,3,5,6] 插入7
6. leetcode 第34题. 在排序数组中查找元素的第一个和最后一个位置 难度:中等
def search_range(alist, target):
if len(alist) == 0:
return (-1, -1)
lbound, rbound = -1, -1
# search for left bound # 搜索该元素出现的第一个位置
left, right = 0, len(alist) - 1
while left + 1 < right:
mid = left + (right - left) // 2
if alist[mid] == target:
right = mid
elif (alist[mid] < target):
left = mid
else:
right = mid
if alist[left] == target:
lbound = left
elif alist[right] == target:
lbound = right
else:
return (-1, -1)
# search for right bound 搜索该元素出现的最后一个位置
left, right = 0, len(alist) - 1
while left + 1 < right:
mid = left + (right - left) // 2
if alist[mid] == target:
left = mid
elif (alist[mid] < target):
left = mid
else:
right = mid
if alist[right] == target:
rbound = right
elif alist[left] == target:
rbound = left
else:
return (-1, -1)
return (lbound, rbound)
7. Search 1st Position of element in Infinite Array(数据流中寻找元素出现的第一个位置)
def binarysearch(alist, item):
if len(alist) == 0:
return -1
left, right = 0, len(alist) - 1
while left + 1 < right:
mid = left + (right - left) // 2
if alist[mid] == item:
right = mid
elif alist[mid] < item:
left = mid
elif alist[mid] > item:
right = mid
if alist[left] == item:
return left
if alist[right] == item:
return right
return -1
# 倍增法:长度两倍两倍的增加
def search_first(alist, target):
left, right = 0, 1
while alist[right] < target:
left = right
right *= 2
if (right > len(alist)):
right = len(alist) - 1
break
return left + binarysearch(alist[left:right+1], target)
8. leetcode第475题. 供暖器
** Solution **
1、找到每个房屋离加热器的最短距离(即找出离房屋最近的加热器)。 2、在所有距离中选出最大的一个max(res)即为结果。
from bisect import bisect
def findRadius(houses, heaters):
heaters.sort()
ans = 0
# 对于每一个房子而言,要找到它左边的最近的供暖设备和它右边的最近的供暖设备
for h in houses:
hi = bisect(heaters, h) # bisect函数返回的是插入索引的位置
left = heaters[hi-1] if hi - 1 >= 0 else float('-inf')
right = heaters[hi] if hi < len(heaters) else float('inf')
ans = max(ans, min(h - left, right - h))
return ans
9. leetcode 第69题. x 的平方根
def sqrt(x):
if x == 0:
return 0
left, right = 1, x
while left <= right:
mid = left + (right - left) // 2
if (mid == x // mid):
return mid
if (mid < x // mid):
left = mid + 1
else:
right = mid - 1
return right
10. leetcode 74. 搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
每行中的整数从左到右按升序排列。 每行的第一个整数大于前一行的最后一个整数。
# Naive: 每一行使用二分搜索寻找 O(nlgm) 每一列使用二分搜索 O(mlgn)
# 法一:二分搜索 O(lgmn)
# 解释: https://leetcode-cn.com/problems/search-a-2d-matrix/solution/sou-suo-er-wei-ju-zhen-by-leetcode/
def search_matrix(matrix, target):
if len(matrix) == 0:
return False
if len(matrix[0]) == 0:
return False
m = len(matrix)
n = len(matrix[0])
left, right = 0, m*n-1 # 看成一个一维的有序数组
while left + 1 < right:
mid_idx = left + (right - left) // 2
mid_element = matrix[mid_idx // n][mid_idx % n]
if mid_element == target:
return True
elif mid_element < target:
left = mid_idx
else:
right = mid_idx
if matrix[left//n][left%n] == target:
return True
if matrix[right//n][right%n] == target:
return True
return False
# 法二:从矩阵的左上角或者右下角搜索 O(m + n)
def search_matrix(matrix, target):
if len(matrix) == 0:
return False
if len(matrix[0]) == 0:
return False
m = len(matrix) - 1 # 行数
n = len(matrix[0]) - 1 # 列数
x,y = m, 0
while(x >= 0 and y <= n):
if matrix[x][y] > target:
x -= 1
elif matrix[x][y] < target:
y += 1
else:
return True
return False
11. leetcode: 第378题. 有序矩阵中第K小的元素 难度:中等
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。
首先第k小数一定落在[l, r]中,其中l = matrix[0][0], r = matrix[row - 1][col - 1]. 我们二分值域[l, r]区间,mid = (l + r) // 2, 对于mid,我们检查矩阵中有多少元素小于等于mid, 记个数为cnt,那么有:
1、如果cnt < k, 那么[l, mid]中包含矩阵元素个数一定小于k,那么第k小元素一定不在[l, mid] 中,必定在[mid + 1, r]中,所以更新l = mid + 1.
2、否则cnt >= k,那么[l, mid]中包含矩阵元素个数就大于等于k,即第k小元素一定在[l,mid]区间中, 更新r = mid;
from bisect import bisect
def kthSmallest(matrix, k):
lo, hi = matrix[0][0], matrix[-1][-1]
while lo < hi:
mid = lo + (hi - lo) // 2
if sum(bisect(row, mid) for row in matrix) < k:
lo = mid + 1
else:
hi = mid
return lo
12. leetcode 第287题. 寻找重复数 难度:中等
# 二分法的思路是先猜一个数(有效范围 [left, right]里的中间数 mid),
#然后统计原始数组中小于等于这个中间数的元素的个数 cnt,
#如果 cnt 严格大于 mid,(注意我加了着重号的部分「小于等于」、
#「严格大于」)。根据抽屉原理,重复元素就在区间 [left, mid] 里;
#链接:https://leetcode-cn.com/problems/find-the-duplicate-number/solution/er-fen-fa-si-lu-ji-dai-ma-python-by-liweiwei1419/
# index: 1 2 3 4 5
# value: 1 3 4 2 2
# 二分法 O(nlgn)
def findDuplicate(nums):
low = 1
high = len(nums)-1
while low < high:
mid = low + (high - low) // 2
count = 0
for i in nums:
if i <= mid:
count += 1
if count <= mid: # 重复元素在[mid, right]区间里面
low = mid+1
else: # 重复元素在[left, mid]区间里面
high = mid
return low

浙公网安备 33010602011771号