一、二分查找的五个模版
1.模版一:基础查找:目标是否存在
java
/* 模板一:基础查找,目标是否存在 */
class Solution {
public int binarySearch(int[] nums, int target) {
int n = nums.length;
if (n == 0)
return -1;
if (target < nums[0] || target > nums[n - 1])
return -1;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
else
return mid;
}
return -1;
}
}
2.模版二:查找左边界(第一个>=target的元素位置)
解法一,边界的更新逻辑:
- 当 nums[mid] < target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的左侧。因此,更新 left = mid + 1。
- 当 nums[mid] >= target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠左的答案,因此,更新 right= mid - 1。
解法一,返回边界 left:
- 由于 mid 满足条件时,总是更新 right,试图寻找更靠左的满足条件的答案,因此,当循环终止时,right 位置一定不满足条件。
- left 要么不发生移动,要么是向右、向待查找区间内移动,即满足条件的区间。
- 因此,当循环终止时,left 停在最靠前的满足条件(>= target)的位置,并且,left 左侧的元素均不满足(均 < target)。
java
/* 模板二:查找左边界(第一个 >= target 的元素位置) */
class Solution {
public int lowerBound(int[] nums, int target) {
int n = nums.length;
if (n == 0)
return 0;
if (target <= nums[0])
return 0;
if (target > nums[n - 1])
return n;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
}
3.模版三:查找大于的左边界(第一个>target的元素位置)
解法一,边界的更新逻辑:
- 当 nums[mid] <= target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的左侧。因此,更新 left = mid + 1。
- 当 nums[mid] > target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠左的答案,因此,更新 right= mid - 1。
解法一,返回边界 left:
- 由于 mid 满足条件时,总是更新 right,试图寻找更靠左的满足条件的答案,因此,当循环终止时,right 位置一定不满足条件。
- left 要么不发生移动,要么是向右、向待查找区间内移动,即满足条件的区间。
- 因此,当循环终止时,left 停在最靠前的满足条件(> target)的位置,并且,left 左侧的元素均不满足(均 <= target)。
java
class Solution {
public int upperBound(int[] nums, int target) {
int n = nums.length;
if (n == 0)
return 0;
if (target < nums[0])
return 0;
if (target >= nums[n - 1])
return n;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target)
left = mid + 1;
else
right = mid - 1;
}
return left;
}
}
4.模版四:查找右边界(最后一个<=target的元素)
解法一,边界的更新逻辑:
- 当 nums [mid] <= target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠右的答案,因此,更新 left = mid + 1。
- 当 nums [mid] > target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的右侧。因此,更新 right。
解法一,返回边界 right
- 由于 mid 满足条件时,总是更新 left,试图寻找更靠右的满足条件的答案,因此,当循环终止时,left 位置一定不满足条件。
- right 要么不发生移动,要么是向左、向待查找区间内移动,即满足条件的区间。
- 因此,当循环终止时,(写法一中的)right 停在最靠后的满足条件(<= target)的位置,并且,right 右侧的元素均不满足(均 > target)。
java
class Solution {
public int biSeLessEqualLast(int[] nums, int target) {
int n = nums.length;
if (n == 0)
return -1;
if (target < nums[0])
return -1;
if (target >= nums[n - 1])
return n - 1;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target)
left = mid + 1;
else
right = mid - 1;
}
return right;
}
}
5.模版五:查找小于的右边界(最后一个 < target 的元素位置)
解法一,边界的更新逻辑:
- 当 nums [mid] < target 时,mid 当前位置满足条件,是潜在候选。为了找到可能更靠右的答案,因此,更新 left = mid + 1。
- 当 nums [mid] >= target 时,mid 当前位置不满足条件,可以排除,mid 位于待查找区间的右侧。因此,更新 right。
解法一,返回边界 right
- 由于 mid 满足条件时,总是更新 left,试图寻找更靠右的满足条件的答案,因此,当循环终止时,left 位置一定不满足条件。
- right 要么不发生移动,要么是向左、向待查找区间内移动,即满足条件的区间。
- 因此,当循环终止时,right 停在最靠后的满足条件(< target)的位置,并且,right 右侧的元素均不满足(均 >= target)。
java
class Solution {
public int biSeLessLast(int[] nums, int target) {
int n = nums.length;
if (n == 0)
return -1;
if (target <= nums[0])
return -1;
if (target > nums[n - 1])
return n - 1;
int left = 0;
int right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return right;
}
}
二、二分查找例题
1.搜索旋转排序数组(33题)
题目要求 O(logN) 的时间复杂度,基本可以断定本题是需要使用二分查找 ,怎么分是关键。
由于题目说数字了无重复,举个例子:
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。
这种情况下,前半部分有序 。因此如果 nums[start] <=target<nums[mid],则在前半部分找 ,否则去后半部分找。
第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。
这种情况下,后半部分有序 。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。
java
class Solution {
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int left=0;
int right=nums.length-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
if(nums[mid]>=nums[left]){
if(target>=nums[left]&&target<nums[mid]){
right=mid-1;
}
else{
left=mid+1;
}
}else{
if (target <= nums[right] && target > nums[mid]) {
left = mid + 1;
} else {
right= mid - 1;
}
}
}
return -1;
}
}
2.在排序数组中查找元素的第一个和最后一个位置(34题)
使用模版二找到第一个位置,使用模版三找到最后一个位置
java
class Solution {
public int[] searchRange(int[] nums, int target) {
int m=-1;
int n=-1;
int start=0,end=nums.length-1;
while(start<=end){
int mid=start+(end-start)/2;
if(nums[mid]>=target){
end=mid-1;
}
else start=mid+1;
}
// 关键校验:如果所有的元素都比target大,start会等于nums.lenghth,
//所以需要校验start是否越界?nums[start]是否等于target?
if (start < nums.length && nums[start] == target) {
m = start; // 只有存在target时,才赋值左边界
} else {
// 左边界都不存在,直接返回[-1,-1],不用找右边界了
return new int[]{-1, -1};
}
start=0;
end=nums.length-1;
while(start<=end){
int mid=start+(end-start)/2;
if(nums[mid]<=target){
start=mid+1;
}
else end=mid-1;
}
n=end;
return new int[]{m,n};
}
}
3.寻找重复数(287题)
我们先累计数值在 [1,⌊n/2⌋]的数字,如果数值在 [1,⌊n/2⌋] 之间的数字个数超过了n/2,就证明重复的值在 [1,⌊n/2⌋]区间,就让end=mid-1,否则就是重复数字的值在后半部分区间,让start=mid+1。
java
class Solution {
public int findDuplicate(int[] nums) {
// 数值范围是1~n(数组长度为n+1),所以start从1开始,而非0
int start = 1;
int end = nums.length - 1;
while (start <= end) {
// 取数值范围的中间值(不是数组索引的中间值)
int mid = start + (end - start) / 2;
// 统计数组中 <= mid 的元素个数
int count = 0;
for (int v : nums) {
if (v <= mid) {
count++;
}
}
// 核心逻辑:如果<=mid的数超过mid个,说明重复值在[start, mid]区间
if (count > mid) {
end = mid - 1; // 收缩右边界,找左半区
} else {
start = mid + 1; // 重复值在[mid+1, end]区间
}
}
// 循环结束时,start == end+1,start就是重复的数字
return start;
}
}
4.搜索二维矩阵(240题)
这就是把二分查找变成二维的,基本的思想没有变。
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;
return searchMatrixs(matrix,0,0,matrix.length-1,matrix[0].length-1,target);
}
public boolean searchMatrixs(int[][] matrix,int x1,int y1,int x2,int y2,int target){
if(x1>x2 || y1>y2) return false;
int x=x1+(x2-x1)/2;
int y=y1+(y2-y1)/2;
if(matrix[x][y]==target) return true;
else if(matrix[x][y]>target) return searchMatrixs(matrix,x1,y1,x-1,y2,target) || searchMatrixs(matrix,x1,y1,x2,y-1,target);
else return searchMatrixs(matrix,x+1,y1,x2,y2,target) || searchMatrixs(matrix,x1,y+1,x2,y2,target);
}
}