参考资料来源灵神在力扣所发的题单,仅供分享学习笔记和记录,无商业用途。
核心思路:本质上是求最大
应用场景:在满足条件的最小值区间内使最大化
检查函数:保证数据都要大于等于答案
补充:为什么需要满足大于等于答案的元素而不是等于答案的元素,因为二分会收敛至最优结果
模板:
bool check() //检查条件
int smallestDivisor(vector<int>& nums, int threshold) {
while(l<r){
mid=l+((r-l)>>1)+1;
if(check(start,d,mid)) l=mid; //满足条件,增大区间尝试找到更好数据
else r=mid-1; //不满足条件,缩小区间积极找到合法答案
}
return l;
力扣题单练习(灵神题单中摘取题目)
题意:
给定一个整数数组表示有n个区间[start[i], start[i] + d]。
要求在每个区间内选取一个数,然后这些数据进行两两组合进行绝对差计算,以最小绝对差为结果
要求怎样在区间内取数让结果最大化,也就是最小绝对差最大化
思路:
合理右边界:选取整数数组中最小值和最大值+d获得最大绝对差
检查函数:判断当前区间对每个区间各取的整数的绝对差是否都大于等于答案。
贪心策略:当区间左边界+答案,小于等于下一个区间右边界说明绝对差值大于等于答案,当前成立。
贪心补充:
因为是升序,所以满足了后一个区间,后面所有区间绝对差值只会越来越大,所以当前成立。
采用当前区间左边界是因为要让它尽可能落到合法区间,如果它都没有满足,当前区间内所有数据都不会满足。
反之说明当前答案不成立,因为至少有一组区间的绝对差小于答案。
class Solution {
public:
//检查函数:判断当前区间对每个区间各取的整数的绝对差是否都大于等于答案。
//贪心策略:当区间左边界+答案,小于等于下一个区间右边界说明绝对差值大于等于答案,当前成立。
//因为是升序,所以满足了后一个区间,后面所有区间绝对差值只会越来越大,所以当前成立。
//采用当前区间左边界是因为要让它尽可能落到合法区间,如果它都没有满足,当前区间内所有数据都不会满足
//反之说明当前答案不成立,因为至少有一组区间的绝对差小于答案。
bool check(vector<int>& nums, int d, int ret){
long long x = LLONG_MIN;
for(long long k:nums){
x=max(k,x+ret);
if(x>k+d) return false; //超出下一个区间右边界说明至少存在一组区间的绝对差小于答案,不满足
}
return true;
}
int maxPossibleScore(vector<int>& start, int d) {
//题意:给定一个整数数组表示有n个区间[start[i], start[i] + d]。
//要求在每个区间内选取一个数,然后这些数据进行两两组合进行绝对差计算,以最小绝对差为结果
//要求怎样在区间内取数让结果最大化,也就是最小绝对差最大化
//合理右边界:选取整数数组中最小值和最大值+d获得最大绝对差
sort(start.begin(),start.end());
int l=0,r=start[start.size()-1]-start[0]+d,mid;
while(l<r){
mid=l+((r-l)>>1)+1;
if(check(start,d,mid)) l=mid;
else r=mid-1;
}
return l;
}
};
题意:
给定一个正整数数组,要求你选取k个元素,并求出任意两元素绝对差的最小值,要求最小值最大化
思路:
合理右边界:最大可能的差值是最大值减最小值
检查函数:选取排序后第一个元素,判断是否有k个满足大于等于答案的元素
贪心策略:如果最小值作为选取值都没有找到满足条件的k个元素,其他更不可能
class Solution {
public:
//检查函数:选取排序后第一个元素,判断是否有k个满足大于等于答案的元素
//为什么需要满足大于等于答案的元素而不是等于答案的元素,因为二分会收敛至最优结果
//贪心策略:如果最小值作为选取值都没有找到满足条件的k个元素,其他更不可能
bool check(vector<int>& nums, int k, int ret){
int cnt=1; //选取数据的数量
int last=nums[0]; //当前选取元素
for(int i=1;i<nums.size();i++){
if(nums[i]-last>=ret){
last=nums[i];
cnt++;
if(cnt==k) return true; //当满足条件的元素等于k个时,说明答案成立
}
}
return false;
}
int maximumTastiness(vector<int>& price, int k) {
//题意:给定一个正整数数组,要求你选取k个元素,并求出任意两元素绝对差的最小值,要求最小值最大化
//合理右边界:最大可能的差值是最大值减最小值
sort(price.begin(),price.end());
int l=0,r=price.back()-price[0],mid;
while(l<r){
mid=l+((r-l)>>1)+1;
if(check(price,k,mid)) l=mid;
else r=mid-1;
}
return l;
}
};
题意:
给定一个大小为 n x n 的二维矩阵 grid。
grid[r][c] = 1 ,则表示一个存在小偷的单元格。grid[r][c] = 0 ,则表示一个空单元格
你最开始位于单元格 (0, 0),可以上下左右移动。返回所有通向单元格 (n - 1, n - 1) 的路径中的 最大安全系数 。
安全系数 定义为:从路径中任一单元格到矩阵中任一小偷所在单元格的 最小 曼哈顿距离。
两个单元格 (a, b) 和 (x, y) 之间的 曼哈顿距离 等于 | a - x | + | b - y | 。
思路:
合理右边界:
安全系数定义为路径上任意点到最近小偷的最小曼哈顿距离。
对于一个n×n的矩阵,任意两点间的最大曼哈顿距离为2n-2(从左上角到右下角)。但由于小偷可能位于矩阵内部,实际的安全系数上限会更小。
在最坏情况下,小偷位于四个角落,安全系数最大为n-1,此时右边界设为n(即矩阵行长度)可以覆盖所有可能的安全系数。
检查函数:判断是否有一条到达终点且路径上的单元格到小偷单元格距离大于等于答案的路径
class Solution {
public:
int d[4][2]={0,1,0,-1,-1,0,1,0};
//检查函数:判断是否有一条到达终点且路径上的单元格到小偷单元格距离大于等于答案的路径
bool check(vector<vector<int>>& dist, int k){
if(dist[0][0]<k) return false;
vector<vector<bool>> vis(dist.size(),vector<bool>(dist[0].size(),false)); //去重
queue<pair<int,int>> q;
vis[0][0]=true;
q.push({0,0});
while(!q.empty()){ //广搜:在所有到达终点的路径中找到一条满足条件的路径
auto [x,y]=q.front();
q.pop();
for(int m=0;m<4;m++){
int i=x+d[m][0];
int j=y+d[m][1];
if( i<0 || i>=dist.size() || j<0 || j>=dist[0].size() || dist[i][j]<k || vis[i][j]) continue;
if(i==dist.size()-1 && j==dist[0].size()-1) return true;
vis[i][j]=true;
q.push({i,j});
}
}
return false;
}
int maximumSafenessFactor(vector<vector<int>>& grid) {
//题意:给定一个大小为 n x n 的二维矩阵 grid。
//grid[r][c] = 1 ,则表示一个存在小偷的单元格。grid[r][c] = 0 ,则表示一个空单元格
//你最开始位于单元格 (0, 0),可以上下左右移动。返回所有通向单元格 (n - 1, n - 1) 的路径中的 最大安全系数 。
//安全系数 定义为:从路径中任一单元格到矩阵中任一小偷所在单元格的 最小 曼哈顿距离。
//两个单元格 (a, b) 和 (x, y) 之间的 曼哈顿距离 等于 | a - x | + | b - y | 。
//合理右边界:安全系数定义为路径上任意点到最近小偷的最小曼哈顿距离。
//对于一个n×n的矩阵,任意两点间的最大曼哈顿距离为2n-2(从左上角到右下角)。但由于小偷可能位于矩阵内部,实际的安全系数上限会更小。
//在最坏情况下,小偷位于四个角落,安全系数最大为n-1,此时右边界设为n(即矩阵行长度)可以覆盖所有可能的安全系数。
vector<vector<int>> dist(grid.size(),vector<int>(grid[0].size(),-1));
queue<pair<int,int>> q;
//记录小偷所在单元格的位置,并重置距离
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[0].size();j++){
if(grid[i][j]==1){
q.push({i,j});
dist[i][j]=0;
}
}
}
//计算每个普通单元格距离小偷单元格的最小距离
while(!q.empty()){
auto [x,y]=q.front();
q.pop();
for(int m=0;m<4;m++){
int i=x+d[m][0];
int j=y+d[m][1];
if(i<0 || i>=dist.size() || j<0 || j>=dist[0].size()) continue;
if(dist[i][j]==-1){
dist[i][j]=dist[x][y]+1;
q.push({i,j});
}
}
}
int l=0,r=grid[0].size(),mid;
while(l<r){
mid=l+((r-l)>>1)+1;
if(check(dist,mid)) l=mid;
else r=mid-1;
}
return l;
}
};
题意:
给定一个整数数组表示每个城市的供电站数量和影响范围。可以额外添加k座供电站
每个供电站可以在一定范围内给所有城市提供电力。要求城市最小电量最大化
思路:
合理右边界:城市最小电量+k为极限边界
检查函数:判断添加k个供电站是否能使数组元素都>=答案
贪心策略:如果当前城市电量小于答案,需要在当前位置+r,也就是右边界添加。这样能保证尽可能的辐射更多的城市
滑动窗口:维护区间内添加的发电站的总电量
class Solution {
public:
//检查函数:判断添加k个供电站是否能使数组元素都>=答案
//贪心策略:如果当前城市电量小于答案,需要在当前位置+r,也就是右边界添加。这样能保证尽可能的辐射更多的城市
//滑动窗口:维护区间内添加的发电站的总电量
bool check(vector<long long>& nums, int r, int k, long long m){
long long cnt=k; //格外发电站建立数量
long long buff=0; //当前位置+r区间内格外建造的发电站的总电量
int n=nums.size();
vector<long long> ans(n,0);
for(int i=0;i<n;i++){
if(i-r-1>=0) buff-=ans[i-r-1]; //维护一个定长滑窗
long long total=buff+nums[i];
if(total>=m) continue;
long long need=m-total;
if(cnt<need) return false;
ans[min(i+r,n-1)]+=need;
buff+=need;
cnt-=need;
}
return true;
}
long long maxPower(vector<int>& stations, int r, int k) {
//题意:给定一个整数数组表示每个城市的供电站数量和影响范围。可以额外添加k座供电站
//每个供电站可以在一定范围内给所有城市提供电力。要求城市最小电量最大化
//合理右边界:城市最小电量+k为极限边界
int n=stations.size();
vector<long long> buff(n+1,0);
for(int i=0;i<n;i++) buff[i+1]=buff[i]+stations[i]; //计算前缀和
//根据每个城市的供电站数量和影响范围获取城市的电量
vector<long long> ans(n);
long long min_num=LLONG_MAX;
for(int i=0;i<n;i++){
int left=max(i-r,0);
int right=min(i+r,n-1);
ans[i]=buff[right+1]-buff[left];
min_num=min(min_num,ans[i]);
}
//二分答案
long long head=0,tail=min_num+k,mid;
while(head<tail){
mid=head+((tail-head)>>1)+1;
if(check(ans,r,k,mid)) head=mid;
else tail=mid-1;
}
return head;
}
};