二分查找的好题

Posted 李嘉图杂文

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找的好题相关的知识,希望对你有一定的参考价值。


题目:峰值元素是指其值大于左右相邻值的元素。

给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设nums[-1] = nums[n] = -∞ 。

示例 1:输入:nums = [1,2,3,1]输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。
示例 2:输入:nums = [1,2,1,3,5,6,4]输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。

解题思路:

  1. 遍历访问,找到第一个peak就返回。

    class Solution {
        public int findPeakElement(int[] nums) {
            int ans = -1;
            for(int i=0;i<nums.length;i++){
                if(isPeak(nums,i)){
                    ans = 0;
                    return i;
                }
            }
        }
        private boolean isPeak(int[] nums,int cur){
            if(cur == 0||cur==nums.length-1return false;
            if((nums[cur-1]<nums[cur])&&(nums[cur+1]<nums[cur])) return true;
            else return false;
        }
    }
  2. 二分查找

    二分查找的前提是,序列必须是有规律的,可以保证我们舍弃掉一半后,剩下 的部分一定包含要寻找的target。

    分析这个序列,我们从局部到整体来看。

    首先确定的一点就是,我们判断一个点是不是peak,要和它的相邻元素比较。

    • 局部观察:

      我们随便画一个峰,不难发现:

      peak的左边,所有的元素都比它后面紧邻的元素小。

      peak的右边,所有的元素都比它后面紧邻的元素大。

      至此,我们可以有一个初步的判断:

      while(left<right){
          int mid = left+(right-left);
          if(isPeak(nums,mid)) return mid;
          /*peak在num[i]的右边,即num[i]在上坡的那一面*/
          if(nums[mid]<nums[i+1]) left = mid;
          /*peak在num[i]的左边,即num[i]在下坡的那一面*/
          else right = mid;
      }
    • 整体分析:

      这样的设想,并不会失败。

      当这个线段是一条单调的线段时,我们会找到最左边或者最右边的元素。这是符合定义的,因为两侧的元素被视为无穷小。

      当线段存在peak时,总能找到peak,找到的peak可能是线段中间的某一点,也可能是两头中的某一个。

    • 细节:

      如果按照下面的代码执行:

      if(nums[mid]<nums[i+1]) left = mid;
      else right = mid;

      考虑数组[1,2]

      //第一轮
      left = 0;
      right = 1;
      mid = 1;

      //第二轮
      left = 0;
      right = 1;
      mid = 1;

      ···

      我们发现在收尾(仅剩两个元素)的时候,会陷入无尽的循环,因为left和right指针不动了。

      收尾的情况可以用数组[2,1]和[1,2]来概括。

      那么我们在细节上,应该让right = mid-1还是left = mid+1?

      如果采用right = mid-1的话。

      考虑数组[1,2]

      //第一轮
      left = 0;
      right = 1;
      mid = 0;
      //第二轮
      left = 0;
      right = 0;
      mid = 0;

      //结果出错,我们把真正的peak舍弃了

      如果采用left = mid+1

      考虑数组[1,2]

      //第一轮
      left = 0;
      right = 1;
      mid = 0;
      //第二轮
      left = 1;
      right = 1;
      mid = 1;

      //结果对了

      考虑数组[2,1]

      //第一轮
      left = 0;
      right = 1;
      mid = 0;
      //第二轮
      left = 0;
      right = 0;
      mid = 0;

      //结果还是对的

      所以,最终实现的代码是:

      class Solution {
          public int findPeakElement(int[] nums) {
              int ans = -1;
              int left = 0;
              int right = nums.length -1;
              while(left<right){
                  int mid = left+(right-left)/2;
                  if(isPeak(nums,mid)){
                      ans = 0;
                      return mid;
                  }
                  if(nums[mid]<nums[mid+1]){
                      left = mid+1;
                      
                  }else{
                      right = mid;
                  }
              }
              if(ans < 0){
                  return (nums[0]>nums[nums.length-1])?0:nums.length-1;
              }
              return left;
          }
          private boolean isPeak(int[] nums,int cur){
              if(cur == 0||cur==nums.length-1return false;
              if((nums[cur-1]<nums[cur])&&(nums[cur+1]<nums[cur])) return true;
              else return false;
          }
      }

二分法套路

  1. 找序列规律,判定舍弃的条件。

  2. 考虑指针移动的策略:

    收尾是关键点。

  3. 模板:

    while(left<right){
        if(/*查找成功*/) {/*成功了*/}
        if(/*target在右边*/){left = mid+1;}
        else{right = mid;}
    }


以上是关于二分查找的好题的主要内容,如果未能解决你的问题,请参考以下文章

二分查找--整理

重排链表 一道无聊的好题

11.10晚间练习赛 一套全场爆零的好题

[NOIP2012 提高组] 疫情控制

BZOJ 1342: [Baltic2007]Sound静音问题 | 单调队列维护的好题

[POJ3057]Evacuation(二分图匹配,BFS,二分,好题)