概述
二分搜索主要思想:在有序数组nums
的给定搜索区间[left, right]
中搜索答案target
,每一次搜索比较nums[mid]
与target
,若相等则找到答案,若不等则可以排除掉一半区间,减少候选集的大小,注意mid
要被排除在下一次搜索区间之外。
二分查找代码框架如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int binarySearch(int[] nums, int target) { int left = 0, right = ...;
while(...) { int mid = left + (right - left) / 2; if (nums[mid] == target) { ... } else if (nums[mid] < target) { left = ... } else if (nums[mid] > target) { right = ... } } return ...; }
|
若nums = [1,2,2,2,3]
,target = 2
,如何用二分搜索方法找到target
出现的左右边界?
上面的代码框架包含了查找精确位置及查找左右边界,需要填充的部分的主要思想是保证搜索区间不能漏掉一个元素,也不能重复一个元素。
当搜索区间左右都是闭区间[left, right]
的时候,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| int binarySearch(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right){ int mid = (right - left) / 2 + left; if (nums[mid] == target){ return mid; } else if (nums[mid] < target){ left = mid + 1; } else if (nums[mid] > target){ right = mid - 1; } }
return -1; }
int leftBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right){ int mid = (right - left) / 2 + left; if (nums[mid] == target){ right = mid - 1; } else if (nums[mid] < target){ left = mid + 1; } else if (nums[mid] > target){ right = mid - 1; } }
if (left < nums.length && nums[left] == target) return left; return -1; }
int rightBound(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left <= right){ int mid = (right - left) / 2 + left; if (nums[mid] == target){ left = mid + 1; } else if (nums[mid] < target){ left = mid + 1; } else if (nums[mid] > target){ right = mid - 1; } }
if (right >= 0 && nums[right] == target) return right; return -1; }
|
规律总结:
当 nums[mid] == target
时,精确查找直接return
,边界查找将mid排除在查找区间之外,找左边界时缩小右边,找右边界时缩小左边;
当 nums[mid] != target
时,将mid
排除在下一次的查找区间之外。
对于边界查找,也可以使 right = nums.length
, 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| int leftBound(int[] nums, int target) { int left = 0; int right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } if (left < nums.length && nums[left] == target) return left; return -1; }
int rightBound(int[] nums, int target) { int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { left = mid + 1; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } }
if (right >= 1 && nums[right - 1] == target) return right - 1; return -1; }
|
此时搜索的区间是左闭右开区间[left, right)
,while()
中用的是<
而不是<=
,因为 [x, x)为空集
当 nums[mid] == target
时,将mid排除在查找区间之外,找左边界时缩小右边,找右边界时缩小左边;
当 nums[mid] != target
时,将mid排除在下一次的查找区间之外
规律总结
- 二分查找每次比较搜索区间的中点,当初始化
right = nums.length - 1
时搜索区间为闭区间[left, right]
,初始化 right = nums.length
时搜索区间为左闭右开区间[left, right)
,二者等效;
while()
中用的是 <
还是 <=
取决于当 left == right
时是否为空集, 确保不遗漏、不重复;
- 当
nums[mid] == target
时,精确查找直接return
,边界查找将mid排除在查找区间之外,找左边界时缩小右边,找右边界时缩小左边;
- 当
nums[mid] != target
时,将mid
排除在下一次的查找区间之外。