二分查找
三种模板
模板 #1 (left <= right)left,right
二分查找的最基础和最基本的形式。 查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。 不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。
模板 #2 (left < right)[left,right)(半闭半开区间)
一种实现二分查找的高级方法。 查找条件需要访问元素的直接右邻居。 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。 保证查找空间在每一步中至少有 2 个元素。 需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。
模板 #3 (left + 1 < right)(left,right)(半闭半开区间)
实现二分查找的另一种方法。 搜索条件需要访问元素的直接左右邻居。 使用元素的邻居来确定它是向右还是向左。 保证查找空间在每个步骤中至少有 3 个元素。 需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素是否符合条件。
时间和空间复杂度:
时间:O(log n) —— 算法时间
因为二分查找是通过对查找空间中间的值应用一个条件来操作的,并因此将查找空间折半,在更糟糕的情况下,我们将不得不进行 O(log n) 次比较,其中 n 是集合中元素的数目。
为什么是 log n?
二分查找是通过将现有数组一分为二来执行的。 因此,每次调用子例程(或完成一次迭代)时,其大小都会减少到现有部分的一半。 首先 N 变成 N/2,然后又变成 N/4,然后继续下去,直到找到元素或尺寸变为 1。 迭代的最大次数是 log N (base 2) 。
空间:O(1) —— 常量空间
虽然二分查找确实需要跟踪 3 个指标,但迭代解决方案通常不需要任何其他额外空间,并且可以直接应用于集合本身,因此需要 O(1) 或常量空间。
模板1应用
x 的平方根
猜数字大小
搜索旋转排序数组
力扣上面有相应的题都可以练习,相对较简单
模板2应用
第一个错误的版本(右边发现第一个突变的元素)
//第二模板可以用于查找flase到true的临界变化点 public int firstBadVersion(int n) { int left=0; int right=n; while(left<right) { int mid=left+(right-left)/2; if(isBadVersion(mid)) { right=mid;//当这时发生了错误版本,right=mid; } else{ left=mid+1;//未发生错误版本 } } return left; } //因为是求未错误->到错误版本时的临界点,所以left=mid+1,而右边的值等于right=mid;
寻找峰值
寻找一个数组某个元素的峰值
//用于查找某个数组或集合内中某个值大于左右俩边的值 public int findPeakElement(int[] nums) { int left=0; int right=nums.length-1; while(left<right) { int mid=left+(right-left)/2; if(nums[mid]<nums[mid+1]) { left=mid+1; } else{ right=mid; } } return left; }
寻找旋转排序数组中的最小值
public int findMin(int[] nums) { int left=0; int right=nums.length-1; while(left<right) { int mid=left+(right-left)/2; if(nums[mid]<=nums[nums.length-1]) { right=mid; } else{ left=mid+1; } } return nums[left]; }
找到 K 个最接近的元素
public List<Integer> findClosestElements(int[] arr, int k, int x) { List<Integer> list=new ArrayList<>(); int left=0; int right=arr.length-k; while(left<right) { int mid=left+(right-left)/2; if(x - arr[mid] > arr[mid + k] - x) { left=mid+1; } else{ right=mid; } } for(int i=left;i<left+k;i++) { list.add(arr[i]); } return list; }
模板三
在排序数组中查找元素的第一个和最后一个位置
public int[] searchRange(int[] nums, int target) { int[] res=new int[2]; res[0]=leftSearch(nums,target); res[1]=rightSearch(nums,target); return res; } private int leftSearch(int[] nums, int target){ if(nums==null||nums.length==0) return -1; int l=0,r=nums.length-1; while(l+1<r){ int mid=l+(r-l)/2; if(nums[mid]<target){ l=mid; } else{ r=mid; } } if(nums[l]==target) return l; if(nums[r]==target) return r; return -1; } private int rightSearch(int[] nums, int target){ if(nums==null||nums.length==0) return -1; int l=0,r=nums.length-1; while(l+1<r){ int mid=l+(r-l)/2; if(nums[mid]<=target){ l=mid; } else{ r=mid; } } if(nums[r]==target) return r; if(nums[l]==target) return l; return -1; }