二分算法刷题

LeetCode二分算法题单

1. 二分查找

需要明确,当mid位置的值确定时,是哪个子区间的所有值能够确定下来
模板34.在排序数组中查找元素的第一和最后一个位置
本题为递增(非严格)数组,可以找到num[i] >= target的第一个位置。

int lower_bound(vector<int>& nums, int target) {
    // 找到num[i] >= target 的第一个位置
    int mid, n = nums.size();
    int l = 0, r = n;           // 左闭右开区间[l, r)
    while (l < r) {
        mid = l + (r - l) / 2;
        if (nums[mid] < target) //[l,mid] < target
            l = mid + 1;        //更新区间为[mid+1, r)
        else                    //[mid, r) >= target
            r = mid;            //更新区间为[l,mid)
    }
    return l;
}
vector<int> searchRange(vector<int>& nums, int target) {
    vector<int> ans(2);
    // 在非递减数组中找到 nums[i] == target 的最后一个位置
    // 等价于找到 nums[i] >= target+1 的第一个位置,然后减一
    int ans1 = lower_bound(nums, target), ans2 = lower_bound(nums, target+1);
    if (ans1 >= ans2)
        ans = {-1, -1};
    else ans = {ans1, ans2-1};
    return ans;
}

2. 二分答案

二分答案的关键在于,1. check函数的编写; 2.当check(mid)的值确定时,是哪个子区间的所有check()值能够确定下来。

2.1 求最小

例题1283.使结果不超过阈值的最小除数
若除数x满足\(\sum_{i=0}^{n}(nums_i / x) <= threshold\),则对任意属于[x, +inf]的除数,都满足上式,故可用二分求满足条件的最小的x。

int check(vector<int>& nums, int p) {
    int sum = 0;
    for (int i = 0; i < nums.size(); i++)
        sum += nums[i] / p;
        sum += nums[i] % p > 0;
    return sum;
}
int smallestDivisor(vector<int>& nums, int threshold) {
    int l = 1, r = 1e6;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (check(nums, mid) > threshold) l = mid + 1;
        else r = mid;
    }
    return l;
}

2.2 求最大

模板2226.每个小孩最多能分到多少糖果
假如每人分x颗糖,则至多可以分给\(\sum_{i=0}^{n-1}(candies[i]/x)\)个小孩,所求为满足\(\sum_{i=0}^{n-1}(candies[i]/x) < k\)的最小值\(x_{min}\),答案为\(x_{min}-1\)

long long check(vector<int>& candies, int x){
    long long sum = 0;
    for(int i = 0; i < candies.size();i++) sum += candies[i]/x;
    return sum;
}
int maximumCandies(vector<int>& candies, long long k) {
    int l = 1, r = 0;
    for(int i = 0; i < candies.size();i++) r = max(candies[i], r);
    while(l <= r){
        int mid = l + (r-l)/2;
        if(check(candies, mid) >= k) l = mid + 1;
        else r = mid-1;
    }
    return r;
}

2.3 最小化最大值

为求最小的变形,核心不变,题目理解和check()函数的编写麻烦很多。
例题2064.分配给商店的最多商品的最小值
设分配给任意商店商品数目的最大值为 \(maxQ\) ,则至少需要 \(\sum_{i=0}^{n-1} ceil(quantities_i/maxQ)\)个商店才能分配完商品,故需要找到满足\(\sum_{i=0}^{n-1} ceil(quantities_i/maxQ) <= n\)\(maxQ\) 的最小值

int check(long long maxQ, vector<int>& quantities){
    int cnt = 0;
    for(int i = 0; i < quantities.size(); i++) cnt += ceil((float)quantities[i]/maxQ);
    return cnt;
}
int minimizedMaximum(int n, vector<int>& quantities) {
    long long l = 0, r = 0;
    for(int i = 0; i < quantities.size(); i++) r += quantities[i];
    l = ceil((float)r/n);
    r++;
    while(l < r){
        long long mid = l + (r-l)/2;
        int cnt = check(mid, quantities);
        if(cnt <= n) r = mid;
        else l = mid+1;
    }
    return l;
}

2.4 最大化最小值

为求最大的变形,核心不变,题目理解和check()函数的编写麻烦很多。
例题3281.范围内整数的最大得分
将区间按升序排序,不妨令得分为\(x\),则若在第\(i\)个区间所选数为\(a_i\),则在\(i+1\)区间所选数\(a_{i+1}\)应为\(max(start_{i+1}, a_{i+1} + x)\),若\(a_{i+1}\)超出第\(i+1\)个区间范围,则该得分不满足条件

int check(int x, vector<int>& start, int d){
        long long pre = start[0];
        for(int i = 1; i < start.size(); i++){
            long long temp = max((long long)start[i], pre + x);
            if(temp > start[i]+d) return 1;
            pre = temp;
        }
        return 0;
    }
    int maxPossibleScore(vector<int>& start, int d) {
        sort(start.begin(), start.end());
        int n = start.size(), l = 0, r = start[n-1]-start[0]+d+1;
        while(l < r){
            int mid = l + (r-l)/2;
            if(check(mid, start, d)) r = mid;
            // 找到的是第一个不满足要求的数
            else l = mid+1;
        }
        return l-1;
    }

2.5 第k小/大

\(k\)小等价于:求最小的\(x\),满足\(≤x\)的数至少有\(k\)个。同理第\(k\)大。
例题668.乘法表中第k小的数
对数字\(x\)而言,\(m*n\)乘法表中\(<=x\)的数的个数有\(\sum_{i=0}^{n-1} min(x/i, n)\)个,\(i∈[1,m]\),则该题为找到满足\(\sum_{i=0}^{n} min(x/i, n) >= k\)的最小的\(x\)

int check(int x, int m, int n, int k){
        int cnt = 0;
        for(int i = 1; i <= m; i++){
            cnt += min(x/i, n);
            if(cnt >= k) return 1;
        }
        return 0;
    }
    int findKthNumber(int m, int n, int k) {
        int l = 1, r = n*m+1;
        while(l < r){
            int mid = l + (r-l)/2;
            if(check(mid, m, n, k)) r=mid;
            else l = mid+1;
        }
        return l;
    }
posted @ 2025-04-22 16:44  zerolt  阅读(14)  评论(0)    收藏  举报