二分法
二分法作为一种常见的查找算法,其实不单单可以只寻找某一个数。
最基本的二分查找
搜索一个数,如果存在,返回其索引,否则返回 -1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int binarySearch(vector<int>& nums, int target) { int left = 0; int right = nums.size(); while (left < right) { int mid = left + ((right - left) >> 1); if (nums[mid] > target) right = mid; else if (nums[mid] < target) left = mid + 1; else return mid; } return -1; }
|
搜索左侧边界

如上图所示,查找数组中最左侧大于或等于3的数也可以使用二分法。首先对整个数组二分,看中间的数是否满足大于或等于3,若满足继续在左侧二分,否则在右半侧二分,一直到结束,得到的所有满足条件的最小下标就是求得的结果。
这个问题与普通二分法查找的区别在于:二分法是使用二分找个一个满足条件的数之后就结果查找,但是这里需要一直二分到最后,然后在所有满足条件的数中比较。
搜索左侧边界的二分搜索算法的具体代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int left_bound(vector<int>& nums, int target) { if (nums.size() == 0) return -1; int left = 0, right = nums.size(); while (left < right) { int mid = left + ((right - left) >> 1); if (nums[mid] == target) right = mid; else if (nums[mid] < target) left = mid + 1; else if (nums[mid] > target) right = mid;
} if(left == nums.size()) return -1; return nums[left] == target ? left : -1; }
|
注意在搜索左侧边界的时候并不是一旦发现nums[mid] == target就直接返回,因为要寻找最左侧满足条件的下标。
简化后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int left_bound(vector<int>& nums, int target) { if (nums.size() == 0) return -1; int left = 0, right = nums.size(); while (left < right) { int mid = left + ((right - left) >> 1); if (nums[mid] >= target) right = mid; else left = mid + 1; } if(left == nums.size()) return -1; return nums[left] == target ? left : -1; }
|
搜索右侧边界
搜索右侧边界其实和搜索左侧边界同理,不同的地方在于,搜索左侧边界时出现nums[mid] == target时right需要向左收缩,而在搜索右侧边界的时候出现nums[mid] == target时是left需要向右收缩。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int right_bound(vector<int>& nums, int target) { int left = 0, right = nums.size();
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(left - 1 < 0) return -1; return nums[left - 1] == target ? left - 1 : -1; } }
|
将nums[mid] == target和nums[mid] < target的情况合并之后的简化版为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int right_bound(vector<int>& nums, int target) { int left = 0, right = nums.size();
while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] <= target) left = mid + 1; else right = mid; } if(left - 1 < 0) return -1; return nums[left - 1] == target ? left - 1 : -1; }
|
attention
不管是搜索单个值还是左右侧边界,此模板中left和right收缩的模式是一样的,即left向右收缩时总是left
= mid + 1,而right收缩时总是right =
mid。为什么 left 的更新必须是 left = mid +
1,当然是为了把 nums[mid]
排除出搜索区间。记住这一点便很容易记下三种不同的模板。
另外,搜索左右侧边界时的返回也不一样,搜索左侧边界的时候返回是left,而在搜索右侧边界的时候返回是left
- 1,因为我们对 left 的更新必须是 left = mid +
1,就是说 while 循环结束时,nums[left] 一定不等于
target 了,而 nums[left-1] 可能是
target。
寻找局部最小值
在一个无序数组arr中,相邻的数都不相同,那么如何在这个数组arr中找到一个局部最小值?这里的极小值定义是比左右两侧都小的数,在数组最左侧则只需要满足比它右侧的数小,在数组最右侧的数只需要满足比它左侧的数小即可。
首先判断arr[0]和arr[n - 1]是否是极小值,若右极小值则直接返回。若都不是极小值那么取最中间的数arr[m],判断arr[m]是否为局部最小值,若为局部最小值则直接返回。

若不为极小值那么它要么比它左侧的数大,要么比它右侧的数大。假设它比左侧的数大,那么就在左半边进行二分,如此一来,就存在了二分的点,直至找到一个局部最小值为止。

这里可以看出来二分法不只是能用在有序数组,在无序的情况下满足特定条件也可以用二分。