20230718-复习-二分专题

38 阅读2分钟

今天把之前练过的二分搜索专题复习一下。二分思想很简单,难点在于边界条件,设不好边界条件很容易搜索不到甚至死循环。同时,搜索对象不仅可以是具体的有序数组、字符串,也可以是一个抽象的标量范围。这块可能出难题基本就都是去搜索一个抽象的标量范围,因为看见这种题有可能第一时间想不起来二分搜索。

leetcode-410 分割数组的最大值

这题hard略微有点名不副实,难点其实就在题意的理解上。上来看到题很容易往dp去想,但题干要求分割成若干个“连续的子数组”,也就是说不能分割成离散的子序列(如果是分割成若干个离散的子序列,感觉可以设dp[i][j]为第i个元素分配到第j个子序列中能达到的最小最大值)。那么进一步,不能dp那比较朴素的思路就是遍历搜索最大值的取值范围,每个取值是否可以的判断标准是从头遍历数组,如果当前子数组的和大于了取值,则数组数量+1,最后判断分割完整个数组需要的数组数量是否超过给定的数量。更进一步,因为取值范围必然是有序的,所以把上述遍历搜索优化为二分搜索。

class Solution {
public:
    bool canSplit(vector<int>& nums, int k, int cap){
        int sum = 0, num_sub = 1;
        for(int i : nums){
            if(i > cap) return false;
            else{
                sum += i;
                if(sum > cap){
                    sum = i;
                    ++num_sub;
                }
            }
            if(num_sub > k) return false;
        }
        return true;
    }
    int splitArray(vector<int>& nums, int k) {
        int n = nums.size();
        int l = 0, r = 1e9;
        while(l < r){
            int mid = (l + r) / 2;
            if(canSplit(nums, k, mid)) r = mid;
            else l = mid + 1;
        }
        return r;
    }
};

leetcode-875 爱吃香蕉的珂珂

这题跟上一题类似,也是去二分搜索待求值的解空间,甚至细节套路也基本是一样的。

class Solution {
public:
    bool eat(vector<int>& piles, int k, int h){
        int sum = 0;
        for(int i : piles){
            if(i <= k) ++sum;
            else sum += i % k == 0 ? i / k : i / k + 1;
            if(sum > h) return false;
        }
        return true;
    }
    int minEatingSpeed(vector<int>& piles, int h) {
        int n = piles.size();
        int l = 0, r = 1e9;
        while(l < r){
            if(l == 0 && r == 1) return 1;
            int mid = (l + r) / 2;
            if(eat(piles, mid, h)) r = mid;
            else l = mid + 1;
        }
        return r;
    }
};