二分——查找峰值

106 阅读2分钟

二分查找常用来快速的在有序数组中查找某个数字。但实际上,哪怕数组是部分有序,也可以利用它的性质通过二分查找加快查找的速度。

1.一个数组左边递增,右边递减,求数组中的最大值?

数组中的数字排列形似一个山峰,我们要找的就是山峰的峰值。最简单粗暴的做法就是遍历数组,找出数组中的最大值,但这样时间复杂度是O(n)。我们可以利用二分来加快查找。对于中间元素nums[mid]:

  1. nums[mid] > nums[mid+1],说明此时我们在山坡上,往左才是数组递增方向,此时我们继续向左查找即可;
  2. 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 查找峰值

这道题与第一题思路类似,但有个别不同。

  1. 首先这道题的数组中有多个峰值,但只需返回一个即可;
  2. 这道题假设了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;
    }
};