写完本文后,发现灵神的讲解非常详细易懂,这里贴上视频。
二分查找的基本思想是将查找区间逐步缩小,每次排除一半的元素。具体步骤如下:
public class BinarySearch { public static int search(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; // 未找到目标值 } }
二分查找适用于以下场景:
查找有序数组中第一个和最后一个出现的目标元素是二分查找的一个变体。在这个问题中,数组可能包含重复的元素,而我们希望找到目标值在数组中的首次出现位置和最后一次出现位置。为了实现这个功能,我们可以分别修改二分查找的逻辑来查找第一个和最后一个出现的目标元素。
public static int findFirst(int[] arr, int target) { int left = 0; int right = arr.length - 1; int result = -1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { result = mid; // 记录当前找到的位置 right = mid - 1; // 继续在左侧查找 } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return result; } public static int findLast(int[] arr, int target) { int left = 0; int right = arr.length - 1; int result = -1; while (left <= right) { int mid = left + (right - left) / 2; if (arr[mid] == target) { result = mid; // 记录当前找到的位置 left = mid + 1; // 继续在右侧查找 } else if (arr[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return result; }
查找元素应该插入的位置是二分查找的另一个变体。在这个问题中,我们希望找到目标值在数组中的插入位置,以保持数组的有序性。为了实现这个功能,我们可以修改二分查找更新边界的逻辑来查找目标值应该插入的位置。
这里附上 Python bisect 模块的源码,可以看到 bisect 模块的实现也是基于二分查找的。
def bisect_left(a, x, lo=0, hi=None, *, key=None): """Return the index where to insert item x in list a, assuming a is sorted. The return value i is such that all e in a[:i] have e < x, and all e in a[i:] have e >= x. So if x already appears in the list, a.insert(i, x) will insert just before the leftmost x already there. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """ if lo < 0: raise ValueError('lo must be non-negative') if hi is None: hi = len(a) # Note, the comparison uses "<" to match the # __lt__() logic in list.sort() and in heapq. if key is None: while lo < hi: mid = (lo + hi) // 2 if a[mid] < x: lo = mid + 1 else: hi = mid # 这里是关键,如果 a[mid] >= x,那么 hi = mid,这样就保证了最后返回的索引 i 满足 a[:i] < x, a[i:] >= x else: while lo < hi: mid = (lo + hi) // 2 if key(a[mid]) < x: lo = mid + 1 else: hi = mid return lo
通过找到应该插入的位置,bisect 包中还提供一个直接插入元素的方法 insort_left
:
def insort_left(a, x, lo=0, hi=None, *, key=None): """Insert item x in list a, and keep it sorted assuming a is sorted. If x is already in a, insert it to the left of the leftmost x. Optional args lo (default 0) and hi (default len(a)) bound the slice of a to be searched. """ if key is None: lo = bisect_left(a, x, lo, hi) else: lo = bisect_left(a, key(x), lo, hi, key=key) a.insert(lo, x)