二分枚举答案判定系列---LeetCode2528等
日期: 2023-03-08
题目1 最大化城市的最小供电站数目
题目描述
给你一个下标从 0 开始长度为 n 的整数数组 stations ,其中 stations[i] 表示第 i 座城市的供电站数目。
每个供电站可以在一定 范围 内给所有城市提供电力。换句话说,如果给定的范围是 r ,在城市 i 处的供电站可以给所有满足 |i - j| <= r 且 0 <= i, j <= n - 1 的城市 j 供电。
|x| 表示 x 的 绝对值 。比方说,|7 - 5| = 2 ,|3 - 10| = 7 。 一座城市的 电量 是所有能给它供电的供电站数目。
政府批准了可以额外建造 k 座供电站,你需要决定这些供电站分别应该建在哪里,这些供电站与已经存在的供电站有相同的供电范围。
给你两个整数 r 和 k ,如果以最优策略建造额外的发电站,返回所有城市中,最小供电站数目的最大值是多少。
这 k 座供电站可以建在多个城市。
示例
输入:stations = [1,2,4,5,0], r = 1, k = 2
输出:5
解释:
最优方案之一是把 2 座供电站都建在城市 1 。
每座城市的供电站数目分别为 [1,4,4,5,0] 。
- 城市 0 的供电站数目为 1 + 4 = 5 。
- 城市 1 的供电站数目为 1 + 4 + 4 = 9 。
- 城市 2 的供电站数目为 4 + 4 + 5 = 13 。
- 城市 3 的供电站数目为 5 + 4 = 9 。
- 城市 4 的供电站数目为 5 + 0 = 5 。
供电站数目最少是 5 。
无法得到更优解,所以我们返回 5 。
输入:stations = [4,4,4,4], r = 0, k = 3
输出:4
解释:
无论如何安排,总有一座城市的供电站数目是 4 ,所以最优解是 4 。
解题思路
看到最大最小或者最小最大,就想是二分枚举答案然后判断是否可行。
这道题还比较麻烦,因为每一个供电站会覆盖一定的范围,所以还需要用到前缀和和差分。
因为这道题的答案要尽可能大,所以当check满足时是把left更新,同时最后返回的是left。
class Solution {
public:
vector<long long> diff;
vector<long long> power;
int K;
int R;
//判断 minPow这个答案是否可行
bool check(long long minPow){
diff = vector<long long>(power.size(),0);
int curK = 0;
long long sum_d = 0;
//开始从左往右枚举
for(int i=0;i<power.size();i++){
sum_d += diff[i];
long long m = minPow - power[i] - sum_d;
if(m > 0){
//选取位置放置发电站,在i处放置m个发电站,但是diff[i]不需要更新了,因为是用sum_d来维护的
curK += m;
if(curK > K) return false;
sum_d += m;
if(i + 2*R + 1 < diff.size())
diff[i + 2*R + 1] -= m; //更新差分
}
}
return true;
}
long long maxPower(vector<int>& stations, int r, int k) {
// 二分 + 贪心 + 前缀和 + 差分
long long ans = 0;
K = k;
R = r;
int len = stations.size();
vector<long long> pre(len+1,0);
//计算前缀和
for(int i=0;i<len;i++){
pre[i+1] = pre[i] + stations[i];
}
//根据前缀和快速计算每一个站点目前的电量
power = vector<long long>(len,0);
for(int i=0;i<len;i++){
power[i] = pre[min(i + r + 1, len)] - pre[ max(0,i - r)];
}
//二分枚举答案
//左开右开
long long left = -1;
long long right = pre[len] + k + 1;
while(left + 1 < right){
long long mid = left + (right - left) / 2;
if(check(mid)) left = mid;
else right = mid;
}
return left;
}
};
题目链接
2528. 最大化城市的最小供电站数目 - 力扣(LeetCode)
题目2 最小化数组中的最大值
题目描述
给你一个下标从 0 开始的数组 nums ,它含有 n 个非负整数。
每一步操作中,你需要:
选择一个满足 1 <= i < n 的整数 i ,且 nums[i] > 0 。 将 nums[i] 减 1 。 将 nums[i - 1] 加 1 。 你可以对数组执行 任意 次上述操作,请你返回可以得到的 nums 数组中 最大值 最小 为多少。
示例
输入:nums = [3,7,1,6]
输出:5
解释:
一串最优操作是:
1. 选择 i = 1 ,nums 变为 [4,6,1,6] 。
2. 选择 i = 3 ,nums 变为 [4,6,2,5] 。
3. 选择 i = 1 ,nums 变为 [5,5,2,5] 。
nums 中最大值为 5 。无法得到比 5 更小的最大值。
所以我们返回 5 。
解题思路
因为这道题的答案要尽可能小,所以check满足时是把right设置为mid
class Solution {
public:
int minimizeArrayValue(vector<int>& nums) {
int len = nums.size();
//判断答案是否可行
auto check = [&](int num) -> bool{
long long increment = 0;
for(int i=nums.size()-1;i>=0;i--){
long long m = nums[i] + increment - num;
if( m > 0){
increment = m;
}else{
increment = 0;
}
}
return increment == 0;
};
int maxVal = 0;
for(int x:nums){
maxVal = max(maxVal,x);
}
int left = -1;
int right = maxVal + 1;
while(left +1 < right){
int mid = (left + right) / 2;
if(check(mid)) right = mid;
else left = mid;
}
return right;
}
};
题目链接
2439. 最小化数组中的最大值 - 力扣(LeetCode)
题目3 最小化两个数组中的最大值
题目描述
给你两个数组 arr1 和 arr2 ,它们一开始都是空的。你需要往它们中添加正整数,使它们满足以下条件:
arr1 包含 uniqueCnt1 个 互不相同 的正整数,每个整数都 不能 被 divisor1 整除 。 arr2 包含 uniqueCnt2 个 互不相同 的正整数,每个整数都 不能 被 divisor2 整除 。 arr1 和 arr2 中的元素 互不相同 。 给你 divisor1 ,divisor2 ,uniqueCnt1 和 uniqueCnt2 ,请你返回两个数组中 最大元素 的 最小值 。
示例
输入:divisor1 = 2, divisor2 = 7, uniqueCnt1 = 1, uniqueCnt2 = 3
输出:4
解释:
我们可以把前 4 个自然数划分到 arr1 和 arr2 中。
arr1 = [1] 和 arr2 = [2,3,4] 。
可以看出两个数组都满足条件。
最大值是 4 ,所以返回 4 。
解题思路
因为这道题的答案要尽可能小,所以check满足时更新right,同时最后返回也是right。
同时这道题涉及到求最大公约数(辗转相除法)和最小公倍数。
class Solution {
public:
long long gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
long long lcm(int a, int b){
return (long long) a / gcd(a,b) * b;
}
int minimizeSet(int divisor1, int divisor2, int uniqueCnt1, int uniqueCnt2) {
long long _lcm = lcm(divisor1,divisor2);
auto check = [&](int num) -> bool{
long long left1 = max((long long)0, (long long)uniqueCnt1 - (num/divisor2 - num/_lcm));
long long left2 = max((long long)0, (long long)uniqueCnt2 - (num/divisor1 - num/_lcm));
long long common = num - num/divisor1 - num/divisor2 + num/_lcm;
return common >= (left1 + left2);
};
long long left = 0;
long long right = (uniqueCnt1 + uniqueCnt2) * 2;
while(left +1 < right){
long long mid = (left + right)/2;
if(check(mid)) right = mid;
else left = mid;
}
return right;
}
};
题目链接
2513. 最小化两个数组中的最大值 - 力扣(LeetCode)
总结
总的来说,在二分找答案时,根据题目条件来判断怎么更新和返回什么。
如果结果要尽可能小,那么就是
while(left +1 < right){
long long mid = (left + right)/2;
if(check(mid)) right = mid;
else left = mid;
}
return right;
如果结果要尽可能大,那么就是
while(left +1 < right){
long long mid = (left + right)/2;
if(check(mid)) left = mid;
else right = mid;
}
return left;
返回的就是check满足时更新的那个值。