二分解决最大化最小值问题

二分的本质思想:

​ 二分的本质思想就是将一个整体拆成两半,选取合适的一半之后再拆,再选,一直循环下去拿到最终想要的结果。

遇到 具有单调性质 的问题,我们优先考虑二分。

我们拿下面一道 LeetCode 第414场周赛 的第二题作为例子详细分析

LeetCode 3281 范围内整数的最大得分

给你一个整数数组start和一个整数d,代表n个区间[start[i], start[i] + d]

你需要选择n个整数,其中第i个整数必须属于第i个区间。所选整数的得分定义为所选整数两两之间的最小绝对差。

返回所选整数的最大可能得分。

示例 1:

输入: start = [6,0,3], d = 2

输出: 4

解释:

可以选择整数 8, 0 和 4 获得最大可能得分,得分为 min(|8 - 0|, |8 - 4|, |0 - 4|),等于 4。

示例 2:

输入: start = [2,6,13,13], d = 5

输出: 5

解释:

可以选择整数 2, 7, 13 和 18 获得最大可能得分,得分为 min(|2 - 7|, |2 - 13|, |2 - 18|, |7 - 13|, |7 - 18|, |13 - 18|),等于 5。

提示:

  • 2 <= start.length <= 105
  • 0 <= start[i] <= 109
  • 0 <= d <= 109
问题分析:

题目要求我们从这n个区间中选择n个数,再求这n个数两两之间最小绝对差的最大值。为方便观察,我们对这n个数进行排序,其实也就是对这n个区间按照区间左端点进行排序,也就是对start[]里的元素从小到大进行排序。

我们记排完序之后的start[]数组为
s t a r t [ a 0 , a 1 , . . . , a n − 1 ] start[a_0,a_1,...,a_{n-1}] start[a0,a1,...,an1]
记区间右端点 b i = a i + d b_i = a_i + d bi=ai+d,那么这n个区间为
[ a 0 , b 0 ] , . . . . , [ a n − 1 , b n − 1 ] [a_0,b_0],....,[a_{n-1},b_{n-1}] [a0,b0],....,[an1,bn1]
记从 [ a i , b i ] [a_i,b_i] [ai,bi]取出的数为 r i r_i ri,最小绝对差为 s c o r e score score,我们知道
r i + 1 > = r i + s c o r e r_{i+1} >= r_i + score ri+1>=ri+score
先枚举几个合适的 s c o r e score score值看看情况,我们发现,如果 s c o r e = 0 score = 0 score=0,那么这n个数是很容易选出来的,如果 s c o r e = 1 score = 1 score=1,也是比较容易的,但当 s c o r e = 2 score = 2 score=2的时候,我们就要考虑一下存不存在两个甚至两个以上的[k,k+1]区间,比如:

​ 第三个区间是 [ 3 , 4 ] [3,4] [3,4],第四个区间也是 [ 3 , 4 ] [3,4] [3,4],那么 r 3 r_3 r3 r 4 r_4 r4的绝对值之差肯定比2小,此时这个 s c o r e score score就取不到2。

s c o r e = 3 , 4 , . . . score = 3,4,... score=3,4,...时,难度会越来越大。

s c o r e score score作为自变量,能否选出这n个数作为因变量,我们可以做出如下图像:

微信图片_20240910000740

可以看出,因变量关于 s c o r e score score是具有单调性的,我们要的是值为 T r u e True True对应的 s c o r e score score的最大值。这可以利用二分来做,如果整数 m m m可以充当 s c o r e score score,那么我们就考虑 s c o r e score scored的右半部分,如果 m m m不可以充当 s c o r e score score,那么我们就考虑左半部分。

现在我们回到如何检测整数 m m m是否可以充当 s c o r e score score的问题上,假设 m m m可以充当 s c o r e score score,那么问题就转化成:

​ 给定 m m m,能否从每个区间中各取一个数,使得任意两数之差的最小值至少为 m m m

贪心地想,第一个数越小,第二个数就越能在区间里,所以 r 0 r_0 r0应当为 a 0 a_0 a0,如果 r 1 r_1 r1的最小取值,即 r 0 + m r_0 + m r0+m,超过了其区间右端点 b 1 b_1 b1,说明这个 m m m大了,我们需要减小 m m m得到右边界right。

又因为 r 1 r_1 r1是在区间 [ a 1 , a 1 + d ] [a_1,a_1+d] [a1,a1+d]里的,所以 r 1 > = a 1 r_1 >= a_1 r1>=a1

现在我们得到关于 r 1 r_1 r1的两个范围:
r 1 > = a 1 r 0 + m < = r 1 < = a 1 + d r_1 >= a_1 \\ r_0+m <=r_1<= a_1 + d r1>=a1r0+m<=r1<=a1+d
按照刚刚的贪心思路,我们假设 r 0 , r 1 , . . . , r i r_0,r_1,...,r_i r0,r1,...,ri这些数的任意两数之差至少为m,现在我要找符合条件的 r i + 1 r_{i+1} ri+1,那就要使得 r i r_i ri尽可能的小嘛,这样我们才更容易找到 r i + 1 r_{i+1} ri+1

所以 m m m要想成为合格的 s c o r e score score,就要满足以下条件:
r 0 = a 0 r i = m a x ( r i − 1 + d , a i )     对任意的 i = 1 , 2 , . . . , n − 1 r i < = a i + d r_0 = a_0 \\ r_i = max(r_{i-1}+d,a_i) ~~~~ 对任意的i = 1,2,...,n-1\\ r_i <= a_i + d r0=a0ri=max(ri1+d,ai)    对任意的i=1,2,...,n1ri<=ai+d

解决代码:

到现在,我们给出检测 m m m是否能成为 s c o r e score score的代码:

int check(int m, vector<int> new_Start, int N, int d) {
    long cur = new_Start[0]; // r0 = new_Start[0]
    for (int i = 1; i < N; i++) {
        int prex = cur;
        cur = max((long)new_Start[i], cur + m);
        if (cur > new_Start[i] + d)
            return 2; // m大了,减小右边界
    }
    // 如果所有区间上,m都满足条件,那我们就增大左边界,挑选尽可能大的m
    return 1; // 增大左边界
 }

之后是主框架二分找最大 s c o r e score score代码:

int maxPossibleScore(vector<int>& start, int d) {
    int N = start.size();

    sort(start.begin(), start.end());
    // 主框架是在二分
    int left = 0;
    int right = (start[N - 1] + d - start[0]) / (N - 1) + 1;
    while (left + 1 < right) {
        int mid = left + (right - left) / 2;
        if (check(mid, start, N, d) == 2)
            right = mid;
        else
            left = mid;
    }
    return left;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东秦小熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值