二分查找常用来快速的在有序数组中查找某个数字。但实际上,哪怕数组是部分有序,也可以利用它的性质通过二分查找加快查找的速度。
1.一个数组左边递增,右边递减,求数组中的最大值?
数组中的数字排列形似一个山峰,我们要找的就是山峰的峰值。最简单粗暴的做法就是遍历数组,找出数组中的最大值,但这样时间复杂度是O(n)。我们可以利用二分来加快查找。对于中间元素nums[mid]:
- 当
nums[mid] > nums[mid+1],说明此时我们在山坡上,往左才是数组递增方向,此时我们继续向左查找即可; - 当
nums[mid] < nums[mid+1],说明向右是递增方向
int findMax(const std::vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) {
// 最大值在mid的右边
left = mid + 1;
} else {
// 最大值在mid的左边,或者mid就是最大值
right = mid;
}
}
return nums[left]; // 或者返回nums[right]
}
需要注意的是:我这里采用的是左开右开区间,当left == right时,即为答案。
2. leetcode162 查找峰值
这道题与第一题思路类似,但有个别不同。
- 首先这道题的数组中有多个峰值,但只需返回一个即可;
- 这道题假设了
nums[-1] == nums[size]为负无穷;
换句话说,有可能出现这样的数据。如[2]或者[2, 1],如果我们此时仍然采用左闭右开的区间去搜索,就容易发生数组越界。主要是这一句:nums[mid] < nums[mid+1]。此时我们只需稍稍修改上面题目的做法,将区间改为左闭右闭即可。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
if(nums.size() == 1) return 0;
int left = 0, right = nums.size()-1;
while(left < right) {
int mid = left + (right - left)/2;
if(nums[mid] < nums[mid+1]) left = mid + 1;
else right = mid;
}
return left;
}
};