zoukankan      html  css  js  c++  java
  • 二分查找

    第4章 二分查找

    4.1 算法解释

    二分查找也常被称为二分法或者折半查找,每次查找时通过将带查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度尾O(n)的数组,二分查找的事件复杂度尾O(log n)。

    ​ 举例来说,给定一个排好序的数组[3, 4, 5, 6, 7],我们希望查找4在不在这个数组内。第一次折半时考虑中位数5,因为5大于4,所以如果4存在于这个数组,那么其必定存在于 5左边这一半。于是我们的查找区间变成了[3, 4, 5]。第二次折半时考虑新的中位数4,正好是我们需要查找的数字。于是我们发现,对于一个长度为5的数组,我们只进行了2次查找。如果是遍历数组,最坏的情况则需要查找5次。

    ​ 我们也可以用更加数学的方式定义二分查找。给定一个在[a, b]区间内的单调函数f(x),若f(a)和f(b)正负性相反,那么必定存在一个解c,使得f(c)=0。在上个例子中,f(x)是离散函数f(x)=x+2,查找4是否存在等价于求f(x)-4=0是否有离散解。因为f(1)-4=3-4=-1<0,f(5)-4=7-4=3>0,且函数在区间内单调递增,因此我们可以利用二分法查找求解。如果最后二分到不能再分的情况,如果只剩一个数字,且剩余区间里不存在满足条件的解,则说明不存在离散解,即4不在这个数组内。

    ​ 具体到代码上,二分查找时区间的左右端取开区间或闭区间绝大多数时候都可以,建议左闭右开。

    4.2 求开方

    1. Sqrt(x) (Easy)

      题目描述

      给定要给非负整数,求它的开方,向下取整。

      输入输出样例

      input: 8
      output: 2
      

      8开方的结果是2.82842...,向下取整既是2。

      题解

      ​ 这道题可以理解为给定一个非负整数a,求f(x)=X^2 - a=0的解。因为只考虑x>=0,所以在定义域上是单调递增的。由于f(0)=-a<=0,f(a)=a^2 -a >=0,对区间[0, a]内使用二分法找到f(x)=0的解。

      ​ 为了防止除以0,a=0单独考虑,然后对区间[1, a]进行二分查找。

      private static int mySqrt1(int a){
              if(a==0) return 0;
              int left=1, right=a, mid, sqrt;
              while(left<=right){
                  mid = left + (right-left)/2;
                  sqrt = a/mid;
                  if(sqrt==mid){
                      return mid;
                  }else if(mid>sqrt){
                      right = mid-1;
                  }else{
                      left = mid+1;
                  }
              }
              return right;
          }
      

      还有一种更快的算法——牛顿迭代法,其公式为Xn+1 = Xn - f(Xn)/f`(Xn)。给定f(x) = x^2 - a = 0,这里的迭代公式为Xn+1 = (Xn + a/Xn)/2:

      private static int mySqrt2(int a){
              long x = a;
              while(x*x > a){
                  x = (x + a/x)/2;
              }
              return (int)x;
          }
      

    4.3 查找区间

    1. Find First and Last Position of Element in Sorted Array(Medium)

      题目描述

      ​ 给定一个增序的整数数组 和一个值,查找该值第一次和最后一次出现的位置。

      输入输出样例

      输入是一个数组和一个值,输出为该值第一次出现的位置和最后一次出现的位置(从0开始),如果不存在则返回-1。

      input: nums = [5, 7, 7, 8, 8, 10], target = 8
      output: [3, 4]
      

      数字8在索引 3第一次出现,索引4最后一次出现。

      题解

      感觉这道题像是java中的indexOf()和lastIndexOf(),但是使用的算法不同。

      private static int[] searchRange(int[] nums, int target){
              if(nums.length==0) return new int[]{-1, -1};
              int first = first_index(nums, target);
              int last = last_index(nums, target) - 1;
              if(first==nums.length||nums[first]!=target){
                  return new int[]{-1, -1};
              }
              return new int[]{first, last};
          }
      
          private static int first_index(int[] nums, int target){
              int left=0, right=nums.length, mid;
              while (left<right){
                  mid = (left+right)/2;
                  if(nums[mid]>=target){
                      right = mid;
                  }else{
                      left = mid+1;
                  }
              }
              return left;
          }
      
          private static int last_index(int[] nums, int target){
              int left=0, right=nums.length, mid;
              while (left<right){
                  mid = (left+right)/2;
                  if(nums[mid]>target){
                      right = mid;
                  }else{
                      left = mid+1;
                  }
              }
              return left;
          }
      

    4.4 旋转数组查找数字

    1. Search in Rotated Sorted Array II(Medium)

      题目描述

      ​ 一个原本增序的数组被首尾相连后按某个位置断开(如[1,2,2,3,4,5] -> [2,3,4,5,,1,2],在第一位和第二位断开),我们称其为旋转数组。给定一个值和一个旋转数组,判断这个值是否存在于这个旋转数组中。

      输出输出样例

      ​ 输入是一个数组和一个值,输出是一个布尔值,表示数组中是否存在该值。

      input: nums = [2, 5, 6, 0, 0, 1, 2], target = 0
      output: true
      

      题解

      ​ 即使数组被旋转过,我们仍然可以利用这个数组的递增性(其仍为单调递增),使用二分查找。对于当前的中点,如果它指向的值小于等于右端,那么说明右区间是排好序的;反之,那么说明左区间是排好序的。如果目标值位于排好序的区间内,我们可以对这个区间继续二分查找;反之,我们对另一半区间继续二分查找。

      ​ 注意,因为数组存在重复数字,如果中点和左端的数字相同,我们不能确定是左区间全部相同,还是右区间完全相同。在这种情况下,我们可以简单地将左端点右移一位,然后继续进行二分查找。

      private static boolean search(int[] nums, int target){
              int start=0, end=nums.length-1, mid;
              while (start<=end){
                  mid = (start+end)/2;
                  if(nums[mid]==target){
                      return true;
                  }
                  if(nums[mid]==nums[start]){
                      // 无法判断哪个区间是增序的
                      start++;
                  }else if(nums[mid]<=nums[end]){
                      // 右区间是增序的
                      if(target<nums[mid] && nums[end]<=nums[mid]){
                          start = mid + 1;
                      }else{
                          end = mid - 1;
                      }
                  }else{
                      // 左区间是增序的
                      if(nums[start]<=target &&target<nums[mid]){
                          end = mid - 1;
                      }else{
                          start = mid + 1;
                      }
                  }
              }
              return false;
          }
      

    4.5 练习

    基础难度

    1. Find Minimum in Rotated Sorted Array II(Medium)

      题目描述

      旋转数组的变形题之一。

      假设以升序排序的数组在事先未知的某个轴上旋转。

      (即[0,1,2,4,5,6,7]可能会变成[4,5,6,7,0,1,2])。

      找到最小的元素。

      该数组可能包含重复项。

      输入输出样例:

      input: nums=[4,5,6,7,0,1,2]
      output: 0
      

      题解

      private static int minimumInRotatedArray(int[] nums){
              int left=0, right=nums.length-1, mid=-1;
              while (left<=right){
                  mid = (left+right)/2;
                  if(nums[mid]==nums[left]){
                      // 无法判断哪个区间是递增的
                      left++;
                  }else if(nums[mid]<=nums[right]){
                      // 右侧区间是递增的
                      left = mid;
                  }else{
                      // 左侧区间是递增的
                      right = mid;
                  }
              }
              return Math.min(nums[left], nums[right]);
          }
      
    2. Single Element in a Sorted Array(Medium)

      题目描述

      在出现独立数之前和之后,奇偶位数的值发生了什么变化?

      给定一个仅由整数组成的排好序的数组,其中每个元素仅出现两次,只有一个元素除外。

      找到只出现一次的这个元素。

      提示:您的解决方案应在O(log n)时间和O(1)空间中运行。

      样例输入输出

      Input: nums = [1,1,2,3,3,4,4,8,8]
      Output: 2
      

      题解

      private static int singleNonDuplicate(int[] nums){
              int left=0, right=nums.length-1, mid;
              while(left < right){
                  mid = (left+right)/2;
                  // 在右侧
                  if ((nums.length-1-mid)%2==1){
                      if(nums[mid]==nums[mid + 1]){
                          right = mid;
                      }else{
                          left = mid+1;
                      }
                  // 在左侧
                  } else{
                      if(nums[mid]!=nums[mid + 1]){
                          right = mid;
                      }else{
                          left = mid+1;
                      }
                  }
              }
              return nums[left];
          }
      

    进阶难度

    1. Median of Two Sorted Arrays(Hard)

      题目描述

      需要对两个数组同时进行二分搜索。

  • 相关阅读:
    java中传值与传引用
    microsofr visual studio编写c语言
    openfile学习笔记
    在 Windows 和 Linux(Gnome) 环境下 从命令界面打开网页的方式
    使用vsphere client 克隆虚拟机
    route命令
    linux rpm问题:怎样查看rpm安装包的安装路径
    【leetcode】415. 字符串相加
    【leetcode】面试题 17.01. 不用加号的加法
    【leetcode】989. 数组形式的整数加法
  • 原文地址:https://www.cnblogs.com/pangqianjin/p/14254124.html
Copyright © 2011-2022 走看看