算法_二分

整数二分

1、概述

注意:单调性一定可以二分,但是二分不一定需要单调性 即二分与单调性无关,二分与二段性有关 即我们可以找到分界点,使得以分界点所在的左右区间分别满足不同的性质

二分的核心:每一次在缩小区间的时候,选择一个答案所在的区间,当区间的长度等于1的时候,答案一定就在里面

二分的步骤:

  • 第一步:找中间值
  • 第二步:判断某个条件,然后选择一个答案所在的区间,缩小左区间或者右区间,最终当区间中只有一个数时,这个数就是答案

2、经典二分题

这道题是一道经典的二分问题,包含了两种不同的情况,即寻找左端点与右端点两种情况

力扣34.排序数组中查找元素的第一个位置和最后一个位置

题解

这道题要求使用O(logn)的做法完成,所以就是要求我们使用二分查找去解决,

2.1第一个位置

找第一个位置

由上面的内容,单调性一定可以二分,由于数组是排好序的(从小到大),即数组具有单调性,所以一定可以使用二分,接下来我们需要找到分界点,使得两个区间一个区间满足要求,一个区间不满足要求即可

就此题来说,我们第一步先找的是元素的第一次出现的位置,根据单调性可以发现,假设我们要找k在数组中第一个出现的位置,那么根据坐标轴我们可以得到下面的性质,第一个K后面的数(不包括K)一定是小于K的,K后面的数(包括K)都是大于等于K的,
在这里插入图片描述

所以,我们的目的就是找到K这个点

根据二分的步骤

1、第一步找中点,这里我们将做左端点记为l,右端点记为r 中点就是 mid = (l+r)/2

2、第二步,这里我们判断什么呢?根据上面的图,我们此时需要找到的分界点是K这个分界点,所以我们判断每次取得中间值时候大于等于K,即当满足绿色的区间时,说明中间值一定在K的右边,那么我们找K的位置,就一定在中间值的左边,所以这时我们要缩小右区间,让r=mid

同理,当中间值小于K时,说明K一定在中间值的左边,且中间值可能就在A这点,我们需要缩小左区间,让l=mid+1,

最终l和r会重合,l的位置就在K所在的位置

代码

            int l = 0;
            int r = len - 1;
            //找到第一个数
            while (l < r) {
    
    
                int mid = l + r >> 1;
                //满足右区间,r=mid
                if (nums[mid] >= k) r = mid;
                else l = mid + 1;
            }

2.2最后一个位置

找第一个位置找的是右端点,最后一个位置我们找左端点

同理,我们找到最后一个K所在位置满足的条件,K所在的位置的前的元素都是小于等于K的,K后面的元素都是大于K的 我们要找到K的位置,跟上面同理

  • 当中间值小于等于K时,K一定在中间值的右边,此时 l=mid
  • 当中间值大于K时,K一定在中间值的左边,此时 r=mid-1

注意:当l=mid时,此时计算mid需要l+r+1,即额外加1,,当某一次满足某个条件时,l和r相差1, 因为Java是向下取整,所以(l+r)/2等于l,如果这次的mid还满足l=mid的条件,此时l=mid,(l+r)/2等于l,就会一直是l和r,会死循环,不会退出,如果我们将l+r+1后除2(r和l相差1)此时是等于r的 下一次循环l=r,r=r,就会退出循环
在这里插入图片描述

                l = 0;
                r = len - 1;
               int  mid = l + r >> 1;
                while (l < r) {
    
    
                    //当 l=mid时,mid需要l+r+1
                    mid = l + r + 1 >> 1;
                    if (nums[mid] <= k) l = mid;
                    else r = mid - 1;
                }

3、二段性的二分

力扣278.第一个错误的版本

题解

此题就是二分的二段性,即以第一个错误版本为分界点开始,前面的版本都是正确版本,后面的都是错误版本,此题我们可以找第一个错误版本(右端点),也可以找到最后一个正确版本(左端点) ,此题只需要找右端点即可

注意:

由于此题的左右端点数值较大,l+r时可能溢出,所以我们计算中间值时让左端点+(右端点-左端点)/2,这样防止溢出

 int mid = l + ((r - l)/ 2);

4、不明显的二分

力扣287.寻找重复数

题解


力扣162.峰值

5、旋转排序数组中的最小值

力扣153.旋转排序数组的最小值I

题解


力扣154.旋转排序数组的最小值II

官方题解+视频

浮点数二分

1、模板

浮点数二分的话不需要考虑区间+1,-1的问题,因为每次都能将区间缩小一半,所以每次分区间时直接将l=mid,或者r=mid即可,且这个终止条件可以认为当 l和r相差10的-6次方时认为l是等于r的,直接跳出循环,具体的要看题目要求

如果题目要求保留n位小数 r-l一般需要设置大于1e-(n+2) 比如此题要求保留6位小数 所以需要r-l>1e-8

bool check(double x) {
    
    /* ... */} // 检查x是否满足某种性质

double bsearch(double l, double r){
    
    
    // eps 表示精度,取决于题目对精度的要求 
    final double eps = 1e-6;   
    while (r - l > eps){
    
        
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

2、应用

AcWing790.数的三次方根

给定一个浮点数 n,求它的三次方根

  • −10000≤n≤10000
  • 结果保留 6 位小数
import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        double d = scanner.nextDouble();
        double l = -10000;
        double r = 10000;
        //保留n位小数 r-l一般需要设置大于1e-(n+2) 比如此题要求保留6位小数 所以需要r-l>1e-8
        while (r - l > 1e-8){
    
    
            double mid = (l + r)/2;
            if(mid * mid * mid > d) r = mid;
            else  l = mid;
        }
        System.out.printf("%.6f",l);
    }
}

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/119679566
今日推荐