五、二分法
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