算法专题 | 二分

二分最简单模版

二分的本质找到丢弃一半的规则
其实二分并不一定局限在有序区间内,只要区间具有二段性就可以二分,即区间具有某个性质可以将区间分为两段,使得每次搜索时都能将区间缩小而且保证答案仍在区间内

例如:
在这里插入图片描述
左边一段的点都满足一个性质:上面的点比nums[n - 1]大
右边一段的点都满足一个性质:上面的点不比nums[n - 1]大
这就是所谓的二段性

二分只有下面两种情况

  • 找大于等于给定数的第一个位置 (满足某个条件的第一个数)
  • 找小于等于给定数的最后一个数 (满足某个条件的最后一个数)

    在这里插入图片描述

二分模板说明

  1. 循环必须是l < r
  2. if判断条件看是不是不满足条件, 然后修改上下界
  3. 若是r = mid - 1, 则前面mid 语句要加1(记住与r平衡就行)
  4. 出循环一定是l == r,所以l和r用哪个都可以
  5. 有的时候如果是单调的,l = mid + 1,一直加到最后一个,此时不用担心越界,因为l == r了,退出循环

代码模版

// 判断条件很复杂时用check函数,否则if后直接写条件即可
bool check(int mid) {
    ...
    return ...;
}
 
// 能二分的题一定是满足某种性质,分成左右两部分
// if的判断条件是让mid落在满足你想要结果的区间内

// 找满足某个条件的第一个数  即右半段
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;  
        else l = mid + 1;
    }
    return l;
}

// 找满足某个条件的最后一个数  即左半段
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

例题

69.x的平方根

在这里插入图片描述

class Solution {
public:
    int mySqrt(int x) {
        int l = 0, r = x;
        while (l < r) {
            // 两个int相加减会溢出 中间加个长整型常量
            // 少用乘法,用除法可以防止溢出
            int mid = l + 1ll + r >> 1; 
            if (mid  <= x / mid) l = mid;  
            else r = mid - 1;
        }
        return l;
    }
};

162. 寻找峰值(不有序,也能二分)

在这里插入图片描述
二分法,我们一般用在有序数组上,那么这个题为什么可以用二分呢?

不管什么情况,之所以能用二分,是因为我们可以根据某个条件,直接抛弃一半的元素,从而使得时间复杂度降到 log 级别。

只要数组中存在一个元素比它相邻元素大,则沿着它一定可以找到一个峰值,这样就可以抛弃另一半了
找到了丢弃一半的规则

在这里插入图片描述

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while(l < r) {
            int mid = l + r >> 1;
            // if (mid == n - 1) return nums[n - 1]; 不用加这句,因为此时l == r
            if (nums[mid] <= nums[mid + 1]) l = mid + 1;
            else r = mid;
        }
        
        return l;
    }
};

33.搜索旋转排序数组(好题)

在这里插入图片描述
在这里插入图片描述
左边一段的点都满足一个性质:上面的点比nums[n - 1]大
右边一段的点都满足一个性质:上面的点不比nums[n - 1]大
这就是所谓的二段性

算法:两次二分(比较自然的想法),先找到最小值点,然后在target所在的区间段进行二分

// 2019年12月28日 星期六 15:55:29
class Solution {
public:
    int search(vector<int>& nums, int target) {
        
        int n = nums.size();
        if (n == 0) return -1;
        int l = 0, r = n - 1;
        if (nums[0] > nums[n - 1]) { // 若发生翻转,找到最小值
            while(l < r) {
                int mid = l + r >> 1;
                if (nums[mid] <= nums[n - 1]) r = mid;
                else l = mid + 1;
            }
            
            // 确定target区间
            if (target <= nums[n - 1]) {
                l = l, r = n - 1;
            } else {
                l = 0, r = r - 1;
            }
        }
        while (l < r) {
            int mid = l + r + 1>> 1;
            if (nums[mid] > target) r = mid - 1;
            else l = mid;
        }
        
        if (nums[l] == target) return l;
        return -1;
        
    }
};
发布了182 篇原创文章 · 获赞 71 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_43827595/article/details/103744894