这里写目录标题
二分查找(普通+左边界+有边界)
具体详解来自力扣:
https://leetcode-cn.com/problems/binary-search/solution/er-fen-cha-zhao-xiang-jie-by-labuladong/
特别鸣谢labuladong dalao的详解!!!
下面是自己修改的二分模板,觉得需要记住
普通二分
也就是怎么找都可以,对应力扣第704题,链接:https://leetcode-cn.com/problems/binary-search/
public int search(int[] nums, int target) {
if(nums.length==0) return -1;
int left = 0, right = nums.length-1;
while(left<=right){
int mid = left+(right-left)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]<target) left=mid+1;
else if(nums[mid]>target) right=mid-1;
}
return -1;
}
左边界二分:
public int search(int[] nums, int target) {
if(nums.length==0) return -1;
int left=0, right=nums.length;//注意,右边界超出
while(left<right){ //左闭右开
int mid = left+(right-left)/2; //取中位数,这么做防止相加过大溢出
if(nums[mid]==target) right=mid; //找到后不返回,将左边界缩小
else if(nums[mid]>target) right=mid; //因为是左闭右开,所以right不能等于mid-1
else if(nums[mid]<target) left=mid+1; //而left必须mid+1
}
if(left==nums.length) return -1;
return nums[left]==target?left:-1; //因为有可能找不到所以这个需要判断再返回
}
右边界二分:
public int search(int[] nums, int target) {
if(nums.length==0) return -1;
int left=0, right=nums.length;//注意,右边界超出
while(left<right){ //左闭右开
int mid = left+(right-left)/2; //取中位数,这么做防止相加过大溢出
if(nums[mid]==target) left=mid+1; //找到后不返回,将右边界缩小
else if(nums[mid]<target) left=mid+1; //而left必须mid+1
else if(nums[mid]>target) right=mid; //因为是左闭右开,所以right不能等于mid-1
}
if(left==0) -1;//防止left=0
return nums[left-1]==target?left-1:-1; //因为有可能找不到所以这个需要判断再返回,并且因为是左闭右开,所以left要减一
}
记忆法:
1、普通二分,左闭右闭,left=0,right=nums.length-1,while(left<=right),找到立刻返回,找不到返回-1;
2、边界二分,左闭右开,left=0,right=nums.length,while(left<right);
3、边界二分时,else if 时 nums[mid]和target,哪边大就往哪里推,比如左边(nums[mid])大于右边(target),就left=mid+1(往右推,改left),相反就right=mid(往左推,当然如果只是普通二分,就right=mid-1)
4、边界二分时可能存在找不到的情况,那就在返回时加上判断,结果等于就返回,不能等就返回-1。。。注意:右边界二分时,是判断left-1
通用二分模板
秒杀所有模板,不过变量定义有点多
原题:x的平方根
class Solution {
public:
int mySqrt(int x) {
// 原始边界
ll el = 0, er = x;
// 遍历用的左右边界
ll l = 0, r = x;
// 用中间变量pre_l和pre_r来防止死循环,剩下的靠业务需求来修改,把更多心思放在业务处理上而不是担心死循环
for(int pre_l=l, pre_r=r; l<=r; pre_l=l, pre_r=r){
ll mid = l+(r-l)/2;
// 按照业务需求来改
ll pow = mid*mid;
if(pow==x) return mid;
else if(pow>x) r = mid - 1;
else if(pow<x) l = mid;
// 如果l和r跟原来一样,那就break
if(pre_l==l && pre_r==r) break;
}
// 按照业务需求来改
// 检查l和r是否超边界,并且是否达到题目的要求,是就返回
if(el<=r && r<=er && r*r<=x) return r;
if(el<=l && l<=er && l*l<=x) return l;
// 毛都没有,返回-1
return -1;
}
};
例题
1、在有序数组(升序)中查找第一个大于等于target的数,且数组中没有重复的数,若是数组中不存在这个数,则返回数组长度
例题:最长上升子序列,力扣300题。
public int fun(int[] arr, int target){
int l=0, r=arr.length;
while(l<r){
int mid = l+(r-l)/2;
if(arr[mid]==target) {//找到一个等于自己的数,就字节返回了
l=mid;
break;
}
else if(arr[mid]>target) r=mid;
else if(arr[mid]<target) l=mid+1;
}
return l;
}
相反,查找数组中最后一个小于target的数,就是上面的代码结果-1.
变式:在有序数组(降序)中查找最后一个大于target的数
对应力扣下一个排列
其实就是将上面的反转过来,需要注意的是找中点时因为整数取商是向下取整的,这里需要在结果加一,代码模板
private int bina(int[] nums, int l, int target){
int r = nums.length-1;
while(l<r){
int mid = l+(r-l)/2+1; //需要在这里加一
if(nums[mid]<=target) r = mid-1;
else if(nums[mid]>target) l=mid;
}
return r;
}
剑指 Offer 11. 旋转数组的最小数字
class Solution {
public int minArray(int[] numbers) {
int left = 0, right = numbers.length-1;
if(numbers[left]<numbers[right]) return numbers[left];
while(left+1<right){
if(numbers[left]==numbers[right]) {
int min = numbers[left];
for(int i=left+1; i<right; i++) min = min>numbers[i]?numbers[i]:min;
return min;
}
int mid = left + (right - left) / 2;
System.out.println(mid);
if(numbers[mid]>numbers[right]) left = mid;
else if(numbers[mid]<=numbers[right]) right = mid;
}
return numbers[right];
}
}
变式:假设按照升序排序的数组在预先未知的某个点上进行了旋转
就是用上面的查找最小的数值的下标后,向左右两边的数组进行查找:
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if(n==0) return -1;
if(nums[0]>target&&target>nums[n-1]) return -1;
int minIndex = min(nums);
// System.out.println(minIndex);
int l = erfen(nums, minIndex, n-1, target);
int r = erfen(nums, 0, minIndex-1, target);
return l!=-1?l:r;
}
public int min(int[] nums){
int l=0, r=nums.length-1;
if(nums[l]<nums[r]) return l;
while(l+1<r){
if(nums[l]==nums[r]){
int []min = new int[]{l,nums[l]};
for(int i=l; i<=r; i++)
if(min[1]<nums[i]){
min[0] = i;
min[1] = nums[i];
}
return min[0];
}
int mid = l+(r-l)/2;
if(nums[mid]>nums[r]) l=mid;
else r=mid;
}
return r;
}
public int erfen(int[] nums, int l, int r, int target){
if(l>r) return -1;
while(l<=r){
int mid = l+(r-l)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]>target) r=mid-1;
else if(nums[mid]<target) l=mid+1;
}
return -1;
}
}