二分查找 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class BinarySearch{ public int [] binarySearch(int [] nums,int target){ int left = 0 ,right = ...; while (...){ int mid = left + (right - left )/ 2 ; if (nums[mid]= = target){ ... }else if (nums[mid]< target){ left = ...; }else if (nums[mid]> target){ right = ...; } } return ...; } }
细节 1.退出循环的条件left<right和left<=right有什么区别?
1 2 3 4 5 6 7 8 9 可知:left < right 的结束条件是left = = right 跳出循环,搜索区间为[left ,right )left <= right 结束条件是left = right + 1 跳出循环,搜索区间为[left ,right ] 解释: 因为当left = = right 已经结束循环了,显而易见当我们取left < right 是无法搜索到right 这个位置。 结论: 当right 取arr.length时取left < right 。当right 取arr.length-1 时取left <= right 。
2.区间缩进left=mid+1和left=mid或(right=mid和right=mid-1)有什么区别?
1 2 3 4 5 6 7 8 可知:left = mid+ 1 或right = mid-1 表示下一轮搜索中搜索区间不包含mid,而left = mid或right = mid是包含mid的。 解释: 这两种方式应用的场景(功能)不一样,left = mid+ 1 或right = mid-1 是正常的二分查找使用到,而left = mid或right = mid则是在一些二分搜索的变体中使用 结论: 正常情况下如果我们只有查询数组中是否存在target时只需要left = mid+ 1 或right = mid-1 即可。
二分搜索target最左最右索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 / / 查找target最左边的值 public int findLeftBinarySearch(int [] arr,int target){ int left = 0 ,right = arr.length; while (left < right ){ int mid = left + (right - left )/ 2 ; if (arr[mid]>= target)right = mid;/ / 找到值依旧往左区间找,right = mid 所以 mid 向左移动 else if (arr[mid]< target) left = mid+ 1 ;/ / 右区间 } return left ;/ / 跳出循环时此时left = = right ,而right 正好是target }/ / 查找target最右边的值 public int findRightBinarySearch(int [] arr,int target){ int left = 0 ,right = arr.length; while (left < right ){ int mid = left + (right - left )/ 2 ; if (arr[mid]> target)right = mid;/ / 左区间 else if (arr[mid]<= target) left = mid+ 1 ;/ / 找到值依旧往右区间找 } return left -1 ;/ / 跳出循环时此时left = = right ,我们使用left 记录target的,所以此时需要-1 }
3.取下标的mid=(left+right)/2和mid=left+(right-left)/2有什么区别?
1 这两种写法本质上没有什么区别,结果都一样,但是后者能防止整数溢出
相关题目
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int mySqrt (int x) { int l = 0 , r = x, ans = -1 ; while (l <= r) { int mid = l + (r - l) / 2 ; if ((long ) mid * mid <= x) { ans = mid; l = mid + 1 ; } else { r = mid - 1 ; } } return ans; } }
牛顿迭代法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int mySqrt (int x) { if (x == 0 ) { return 0 ; } double C = x, x0 = x; while (true ) { double xi = 0.5 * (x0 + C / x0); if (Math.abs(x0 - xi) < 1e-7 ) { break ; } x0 = xi; } return (int ) x0; } }
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public boolean isPerfectSquare (int num) { int l = 0 , r = num, ans = -1 ; while (l <= r) { int mid = l + (r - l) / 2 ; if ((long ) mid * mid == num) { return true ; }else if ((long ) mid * mid <= num) { ans = mid; l = mid + 1 ; } else { r = mid - 1 ; } } return false ; } }
将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。 此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int search (int [] nums, int target) { if (nums.length==0 )return -1 ; if (nums.length==1 )return nums[0 ]==target?0 :-1 ; int left = 0 ,right = nums.length-1 ; while (left<=right){ int mid = (left+right)/2 ; if (nums[mid]==target)return mid; if (nums[0 ]<=nums[mid]){ if (nums[0 ]<=target&⌖<nums[mid])right=mid-1 ; else left=mid+1 ; }else { if (nums[nums.length-1 ]>=target&⌖>nums[mid])left=mid+1 ; else right=mid-1 ; } } return -1 ; } }
二分查找,将其看做一个有序一维数组,row = idx / n , col = idx % n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public boolean searchMatrix (int [][] matrix, int target) { int m = matrix.length; if (m == 0 ) return false ; int n = matrix[0 ].length; int left = 0 , right = m * n - 1 ; int pivotIdx, pivotElement; while (left <= right) { pivotIdx = (left + right) / 2 ; pivotElement = matrix[pivotIdx / n][pivotIdx % n]; if (target == pivotElement) return true ; else { if (target < pivotElement) right = pivotIdx - 1 ; else left = pivotIdx + 1 ; } } return false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean searchMatrix (int [][] matrix, int target) { int x=matrix.length-1 ,y=0 ; while (x>=0 &&y<matrix[0 ].length){ if (matrix[x][y]==target){ return true ; } if (matrix[x][y]>target){ x--; }else { y++; } } return false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int search (int [] nums, int target) { int n = nums.length; if (n == 0 ) { return -1 ; } if (n == 1 ) { return nums[0 ] == target ? 0 : -1 ; } int l = 0 , r = n - 1 ; while (l <= r) { int mid = (l + r) / 2 ; if (nums[mid] == target) { return mid; } if (nums[0 ] <= nums[mid]) { if (nums[0 ] <= target && target < nums[mid]) { r = mid - 1 ; } else { l = mid + 1 ; } } else { if (nums[mid] < target && target <= nums[n - 1 ]) { l = mid + 1 ; } else { r = mid - 1 ; } } } return -1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public boolean increasingTriplet (int [] nums) { int n = nums.length; if (n < 3 ) { return false ; } int [] leftMin = new int [n]; leftMin[0 ] = nums[0 ]; for (int i = 1 ; i < n; i++) { leftMin[i] = Math.min(leftMin[i - 1 ], nums[i]); } int [] rightMax = new int [n]; rightMax[n - 1 ] = nums[n - 1 ]; for (int i = n - 2 ; i >= 0 ; i--) { rightMax[i] = Math.max(rightMax[i + 1 ], nums[i]); } for (int i = 1 ; i < n - 1 ; i++) { if (nums[i] > leftMin[i - 1 ] && nums[i] < rightMax[i + 1 ]) { return true ; } } return false ; } public boolean increasingTriplet (int [] nums) { int n = nums.length; if (n < 3 ) return false ; int first = nums[0 ], second = Integer.MAX_VALUE; for (int i = 1 ; i < n; i++) { int num = nums[i]; if (num > second) { return true ; } else if (num > first) { second = num; } else { first = num; } } return false ; } }
一般只要是要求时间复杂度是O(log(m+n))的都是使用二分查找来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public double findMedianSortedArrays (int [] nums1, int [] nums2) { if (nums1.length > nums2.length) { int [] temp = nums1; nums1 = nums2; nums2 = temp; } int m = nums1.length; int n = nums2.length; int leftTotal = (m + n + 1 ) / 2 ; int left = 0 , right = m; while (left < right) { int i = left + (right - left + 1 ) / 2 ; int j = leftTotal - i; if (nums1[i - 1 ] > nums2[j]) { right = i - 1 ; } else { left = i; } } int i = left; int j = leftTotal - left; int nums1LeftMax = (i == 0 ) ? Integer.MIN_VALUE : nums1[i - 1 ]; int nums2LeftMax = (j == 0 ) ? Integer.MIN_VALUE : nums2[j - 1 ]; int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i]; int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j]; if (((m + n) % 2 ) == 1 ) return Math.max(nums1LeftMax, nums2LeftMax); else return (double ) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2 ; }
see:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/he-bing-yi-hou-zhao-gui-bing-guo-cheng-zhong-zhao-/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Solution { public double findMedianSortedArrays (int [] nums1, int [] nums2) { int leftLength = nums1.length; int rightLength = nums2.length; if (leftLength > rightLength) { return findMedianSortedArrays(nums2, nums1); } int totalLeft = (leftLength + rightLength + 1 ) / 2 ; int left = 0 ; int right = leftLength; while (left < right) { int i = left + (right - left + 1 ) / 2 ; int j = totalLeft - i; if (nums1[i - 1 ] > nums2[j]) { right = i - 1 ; } else { left = i; } } int i = left; int j = totalLeft - i; int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1 ]; int nums1RightMin = i == leftLength ? Integer.MAX_VALUE : nums1[i]; int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1 ]; int nums2RightMin = j == rightLength ? Integer.MAX_VALUE : nums2[j]; if (((leftLength + rightLength) % 2 ) == 1 ) { return Math.max(nums1LeftMax, nums2LeftMax); } else { return (double ) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2 ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { / / 思路:O(logn)二分查找target左右边界 / / 如何查找边界:查找左边界,查找到值继续向左区间搜索,直到退出循环,查找右边届同理 public int [] searchRange(int [] nums, int target) { int left = 0 ,right = nums.length-1 ; int [] ans = {-1 ,-1 }; if(nums.length= = 0 )return ans; while(left < right ){ int mid = left + (right - left )/ 2 ; if(nums[mid]>= target)right = mid; else left = mid + 1 ; } if(nums[left ]!= target)return ans; ans[0 ] = left ; right = nums.length; while(left < right ){ int mid = left + (right - left )/ 2 ; if(nums[mid]<= target)left = mid + 1 ; else right = mid; } ans[1 ] = left - 1 ; return ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { / / 思路:二分变体 / / 查找第一个递减的区间 public int findMin(int [] nums) { int left = 0 ,right = nums.length-1 ; if(nums[0 ]< nums[right ]|| right = = 0 )return nums[0 ]; while(left <= right ){ int mid = left + (right - left )/ 2 ; if(nums[mid]> nums[mid+ 1 ])return nums[mid+ 1 ]; if(nums[mid-1 ]> nums[mid])return nums[mid]; if(nums[left ]< nums[mid]){ left = mid+ 1 ; }else { right = mid-1 ; } } return nums[0 ]; } / / 旋转后数组可能是的状态: / / 1. 整个数组呈递增状态(旋转了n次n= num.length) / / 2. 数组呈现两段(左段、右段)递增,必定是左段的数组都大于右段 / / 针对上述两种情况(2 其实包含了1 ),mid可能出现的情况: / / 1. nums[mid]< nums[left ],说明mid在右段,且因为右段的所有数必定小于左段,所以min必定在(left ,mid]区间,令right = mid / / 2. nums[mid]>= num[left ],说明mid在左段,而左段必定大于右段,所以 min必定在(mid,right ]区间,令left = mid + 1 public int findMin(int [] nums) { int left = 0 , right = nums.length - 1 ; while(left < right ){ int mid = left + (right - left )/ 2 ; if(nums[mid]< nums[right ]){ right = mid; }else { left = mid + 1 ; } } return nums[left ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { / / 思路:O(log n) 二分查找 / / 条件:nums[-1 ] = nums[n] = - ∞ / / 证明:如果存在nums[mid] < nums[mid+ 1 ] 基于条件可知往右边继续查找必定能找到右峰底 / / 会有两种情况 / / 1. nums[mid+ 2 ]直接小于nums[mid+ 1 ],则峰顶就是nums[mid+ 1 ] / / 2. nums[mid+ 1 ,n-1 ]都是升序,基于条件,那么是右峰底就是nums[n],此时nums[n-2 ],nums[n-1 ],nums[n]构成山峰 / / 结论: / / 1. 如果nums[i] > nums[i+ 1 ],则在i之前一定存在峰值元素 / / 2. 如果nums[i] < nums[i+ 1 ],则在i+ 1 之后一定存在峰值元素 public int findPeakElement(int [] nums) { int left = 0 ,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 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { / / 横向比较 public String longestCommonPrefix(String[] strs) { StringBuilder sb = new StringBuilder(); for (int i= 0 ;i< strs[0 ].length();i+ + ){ char c = strs[0 ].charAt(i); for (String s:strs){ if(s.length()<= i|| s.charAt(i)!= c)return sb.toString(); } sb.append(c); } return sb.toString(); } / / 二分查找 public String longestCommonPrefix0(String[] strs) { if (strs = = null || strs.length = = 0 ) return ""; int minLength = Integer.MAX_VALUE; for (String str : strs) minLength = Math.min (minLength, str.length()); int low = 0 , high = minLength; while (low < high) { int mid = (high - low + 1 ) / 2 + low; if (isCommonPrefix(strs, mid)) { low = mid; } else { high = mid - 1 ; } } return strs[0 ].substring (0 , low); } public boolean isCommonPrefix(String[] strs, int length) { String str0 = strs[0 ].substring (0 , length); int count = strs.length; for (int i = 1 ; i < count; i+ + ) { String str = strs[i]; for (int j = 0 ; j < length; j+ + ) { if (str0.charAt(j) != str.charAt(j)) { return false ; } } } return true ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { / / 暴力破解 public int minSubArrayLen(int s, int [] nums) { int n = nums.length; if (n = = 0 ) return 0 ; int ans = Integer.MAX_VALUE; for (int i = 0 ; i < n; i+ + ) { int sum = 0 ; for (int j = i; j < n; j+ + ) { sum + = nums[j]; if (sum >= s) { ans = Math.min (ans, j - i + 1 ); break; } } } return ans = = Integer.MAX_VALUE ? 0 : ans; } / / 前缀和+ 二分查找 public int minSubArrayLen(int s, int [] nums) { int n = nums.length; if (n = = 0 ) return 0 ; int ans = Integer.MAX_VALUE; int [] sums = new int [n + 1 ]; / / 为了方便计算,令 size = n + 1 / / sums[0 ] = 0 意味着前 0 个元素的前缀和为 0 / / sums[1 ] = A[0 ] 前 1 个元素的前缀和为 A[0 ] / / 以此类推 for (int i = 1 ; i <= n; i+ + ) sums[i] = sums[i - 1 ] + nums[i - 1 ]; for (int i = 1 ; i <= n; i+ + ) { int target = s + sums[i - 1 ]; int bound = Arrays.binarySearch(sums, target); if (bound < 0 ) { bound = - bound - 1 ; } if (bound <= n) { ans = Math.min (ans, bound - (i - 1 )); } } return ans = = Integer.MAX_VALUE ? 0 : ans; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { / / 思路:二分查找 / / 二分每次将分为两个部分,通过mid的奇偶判断在左右区间,如果mid% 2 = = 0 && nums[mid]= = nums[mid+ 1 ]说明在右区间反之 public int singleNonDuplicate(int [] nums) { int n = nums.length; int left = 0 ,right = n - 1 ; while(left <= right ){ int mid = left + (right - left )/ 2 ; if(mid% 2 = = 0 ){ if(mid< n-1 && nums[mid]= = nums[mid+ 1 ]){ left = mid + 2 ; }else { right = mid - 2 ; } }else { if(mid> 0 && nums[mid]= = nums[mid-1 ]){ left = mid + 1 ; }else { right = mid - 1 ; } } } return nums[left ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution { / / 思路:根据数的位数和数的个数可知 / / 1 ~ 9 有 1 位* 9 个= 9 个 / / 10 ~ 99 有 2 位* 90 个= 180 个 / / 100 ~ 999 有 3 位* 900 个= 2700 个 / / 由此可以来确定n具体落在那个数上,通过二分不断确定其所在的位置 / / 例如如查找407 / / 通过上面的递推可知其区间在(100 ,999 ) public int findNthDigit(int n) { / / 二分查找所在的范围 int low = 1 ,height = 9 ; while (low< height){ int mid = low + (height- low)/ 2 ; if(totalDigit(mid)< n){ low = mid+ 1 ; }else { height = mid; } } / / 右一个长度拥有的数的数量 int d = low; / / 前一个长度拥有的数的数量 int prevDigits = totalDigit(d-1 ); / / 获取n在数字位数为d的所在下标(单个字符)的位置(100 ,101 ,102 ),-1 是为了下标从0 开始 int index = n - prevDigits - 1 ; / / 左区间,如100 ,1000 int start = (int )Math.pow(10 ,d-1 ); / / 在第几个数(整数) int num = start + index / d; / / 取模得出在一个整数数中的第几位(单个字符)数。如100 ,d= 2 则100 % 2 = 0 ,在第0 位 int digitIndex = index % d; / / 得出在第几个数(num)的下标为第几位(digitIndex)即可求出答案 如132 的下标为2 位,(132 / (3 -2 -1 ))% 10 = 2 int digit = (num/ (int ) (Math.pow(10 ,d - digitIndex -1 ))) % 10 ;/ / return digit; } / / 获取长度为length所包含的数的量 public int totalDigit(int length){ int digit = 0 ; int curLength = 1 ,curCount = 9 ; while (curLength<= length){ digit+ = curLength* curCount; curLength+ + ; curCount* = 10 ; } return digit; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Solution { public int findInMountainArray (int target, MountainArray mountainArr) { int n = mountainArr.length(); int left = 0 ,mid = 0 ,right = n - 1 ; while (left<right){ mid = left+(right-left)/2 ; int midN = mountainArr.get(mid); int midL = mountainArr.get(mid-1 ); int midR = mountainArr.get(mid+1 ); if (midL<midN&&midR<midN)break ; else if (midL<midN&&midR>midN){ left = mid; }else { right = mid; } } int l = 0 ,r = mid; while (l<=r){ int m = l+(r-l)/2 ; int mN = mountainArr.get(m); if (mN==target)return m; else if (mN>target)r=m-1 ; else if (mN<target)l=m+1 ; } int le = mid,ri = n-1 ; while (le<=ri){ int m = le+(ri-le)/2 ; int mN = mountainArr.get(m); if (mN==target)return m; else if (mN>target)le=m+1 ; else if (mN<target)ri=m-1 ; } return -1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int minEatingSpeed (int [] piles, int h) { int n = piles.length; int left = 1 ; int max = 0 ; for (int p:piles)max=Math.max(max,p); int right = max; while (left<right){ int mid = (left+right)>>>1 ; int needHourse = 0 ; for (int p:piles)needHourse+=(p-1 )/mid+1 ; if (needHourse>h){ left = mid + 1 ; } else { right = mid; } } return left; } }
1
2
3
4
5
6
2
4
6
8
10
12
3
6
9
12
15
18
4
8
12
16
20
24
5
10
15
20
25
30
6
12
18
24
30
36
求第几小 等价于求有多少数字不超过 x(求x) 。我们可以遍历乘法表的每一行,对于乘法表的第 i 行,其数字均为 i 的倍数 ,因此不超过 x 的数字有 所以整个乘法表不超过 x 的数字个数为 由于 时 上式可化简为 由于 x 越大上式越大,x 越小上式越小,因此我们可以二分 x 找到答案,二分的初始边界为乘法表的元素范围,即 [1,mn]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int findKthNumber (int m, int n, int k) { int left = 1 , right = m * n; while (left < right) { int x = left + (right - left) / 2 ; int count = x / n * n; for (int i = x / n + 1 ; i <= m; ++i) { count += x / i; } if (count >= k) { right = x; } else { left = x + 1 ; } } return left; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public int smallestDistancePair (int [] nums, int k) { int n = nums.length; Arrays.sort (nums); int left = 0 ,right = nums[n-1 ]-nums[0 ]; while (left<right){ int mid = left+(right-left)/2 ; int count = countLessEquals (nums,mid); if (count<k){ left = mid + 1 ; }else { right = mid; } } return left; } public int countLessEquals (int [] nums,int threshold) { int count = 0 ; int length = nums.length; for (int left = 0 , right = 0 ; right < length; right++) { while (nums[right] - nums[left] > threshold) { left++; } count += right - left; } return count; } }
相关资料