题目列表
3105. 最长的严格递增或递减子数组
3106. 满足距离约束且字典序最小的字符串
3107. 使数组中位数等于 K 的最少操作数
3108. 带权图里旅途的最小代价
一、最长的严格递增或递减子数组
按照题目要求进行模拟即可,这里提供两者思路:
1、两次遍历---我们单独考虑单调递增和单调递减的情况,分组循环就能轻松搞定,这里不多说
2、一次遍历,如何去思考?其实我们在考虑单调性的时候,可以把点连接成线,得到一个折线图(或者把它想象成一个函数),单调增和单调减不会相互影响,它们是独立的,所以我们就可以在一次遍历中统计上坡折线的长度和下坡折线的长度,具体看代码
代码如下
// 分两次遍历,分别计算递增和递减
class Solution {
public:
int longestMonotonicSubarray(vector<int>& nums) {
int ans = 1, n = nums.size();
for(int i=0;i<n;){
int j=i++;
while(i<n&&nums[i]>nums[i-1])
i++;
ans=max(i-j,ans);
}
for(int i=0;i<n;){
int j=i++;
while(i<n&&nums[i]<nums[i-1])
i++;
ans=max(i-j,ans);
}
return ans;
}
};
//一次遍历,同时计算递增和递减
class Solution {
public:
int longestMonotonicSubarray(vector<int>& nums) {
int ans = 1, n = nums.size();
for(int i=1;i<n;){
if(nums[i]==nums[i-1]) {
i++;
continue;
}
// false - nums[i-1]>nums[i]
// true - nums[i-1]<nums[i]
bool flag = nums[i-1]<nums[i]; // 标记当前求的是递增还是递减
int j = i++;
while(i<n&&nums[i]!=nums[i-1]&&(nums[i-1]<nums[i])==flag){
i++;
}
ans=max(i-j+1,ans);
}
return ans;
}
};
二、满足距离约束且字典序最小的字符串
这题是简单的贪心:优先考虑将字符串左边的字符向'a'进行变换,这样得到的字符串的字典序是最少的,同时,由于26个字母的变换是环形的,我们还要考虑是向前直接变换成'a'的操作次数少,还是向后变换到'z',再到'a'的操作次数少,综上两点,代码如下
class Solution {
public:
string getSmallestString(string s, int k) {
for(auto& e:s){
int x = e - 'a'; // 向前转到'a'的操作次数
int y = 'z' - e + 1; // 向后转到'z'再转到'a'的操作次数
int s = min(x,y);
if(s<=k){
e = 'a';
k -= s;
}else{
e -= k; // 如果无法转换到'a',就尽可能向'a'靠
break;
}
}
return s;
}
};
三、使数组中位数等于K的最少操作次数
题目要求通过+1-1操作改变数组的中位数,同时操作次数最少。根据贪心,我们可以反向考虑如何让更多的数字不被操作,即哪些数字本身不会对要改变的中位数产生影响。当i<=mid&&nums[i]<=k || i>=mid&&nums[i]>k时(mid代表中位数下标),我们不用对进行操作。同时,我们让不符合条件的数变得符合条件即可
代码如下
class Solution {
public:
long long minOperationsToMakeMedianK(vector<int>& nums, int k) {
int n = nums.size();
ranges::sort(nums);
long long ans = 0;
int mid = n/2; // 奇数 - 中间数下标,偶数 - 靠右的中间数下标
if(nums[mid]<k){
for(int i=mid;i<n;i++){
if(nums[i]>=k)
break;
ans += k-nums[i];
}
}else if(nums[mid]>k){
for(int i=mid;i>=0;i--){
if(nums[i]<=k)
break;
ans += nums[i]-k;
}
}
return ans;
}
};
这里在基于当前问题提出一个更有难度的问题,如果我们要处理的不是将数组的中位数变成k,而是变成k1,k2,k3...,即有多个询问要处理时,我们该怎么做?
如果直接复用上面的代码,我们的时间复杂度为O(n*m),n为数组长度,m为询问的个数。能否优化?其实在上诉代码中,耗时主要是在for循环求区间和,我们可以用前缀和预处理数组,然后用二分快速找到我们需要的区间的左端点/右端点,最后在O(1)的时间内得到答案,时间复杂度为O(mlogn) 【有兴趣的可以自行实现一下】
四、带权图里旅途的最小代价
这题的关键点:
1、&操作的性质 --- 参与&运算的数字越多,得到的结果越小
2、 可以多次访问同一条边或点
(对于&的性质就不做太多说明,不明白的,可以找几个数算一下,验证一下)
题目要求旅费最少,那么我们直接将这两个点所在的连通图中的所有路径都走一遍/多遍(&同一个数不会影响运算结果),得到的旅费必然是最少的。共有如下情况
- 如果两个点是连通的,那么答案就是两个点所在连通图的所有边权&的结果(也就是说在同一个连通图上的任意两点的最小旅费相同,我们可以预处理)
- 如果两个点不连通,即无法到达,则答案为-1
注意:如果给的两个点相同,答案为0
将点按照图连不连通进行划分,很标准的并查集的题,代码如下
class Solution {
public:
vector<int> minimumCost(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
// dist代表旅费,赋值为-1是因为-1的二进制表示为全1,不会影响&的运算结果
vector<int> fa(n,-1),dist(n,-1);
// 并查集最核心的函数
function<int(int)>find=[&](int x)->int{
return fa[x]==-1?x:fa[x]=find(fa[x]);
};
for(auto&e:edges){
int x = e[0], y = e[1], w = e[2];
int fa_x = find(x), fa_y = find(y);
if(fa_x!=fa_y){
fa[fa_x]=fa_y; // 合并两个点所在的集合,将fa_y当作最终的父节点
dist[fa_y]&=dist[fa_x]; // 计算旅费 --- 注意是谁是最终的父节点,如果想不明白,可以加上一行 dist[fa_x]=dist[fa_y]
}
dist[fa_y]&=w; // 同上
}
int m = query.size();
vector<int> ans(m);
for(int i=0;i<m;i++){
int x = query[i][0], y = query[i][1];
int fa_x = find(x), fa_y = find(y);
if(fa_x!=fa_y) ans[i] = -1;
else ans[i] = x==y?0:dist[fa_x];
}
return ans;
}
};
这里除了并查集,我们也可以用简单的dfs来解决问题,思想和并查集类似,这里就不多介绍了,代码中的细节还是值得品味的,建议也学一学这种做法。
class Solution {
public:
vector<int> minimumCost(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<pair<int,int>>>g(n);
for(auto e:edges){
g[e[0]].emplace_back(e[1],e[2]);
g[e[1]].emplace_back(e[0],e[2]);
}
vector<int> mask(n,-1);
vector<int> dist;
// 注意题目中给的图是允许出现环的
function<int(int)>dfs=[&](int x)->int{
mask[x] = dist.size();
int v = -1;
for(auto [y,w]:g[x]){
v &= w;
if(mask[y] < 0){
v &= dfs(y);
}
}
return v;
};
for(int i = 0; i < n; i++){
if(mask[i]==-1){
dist.push_back(dfs(i));
}
}
int m = query.size();
vector<int> ans(m);
for(int i=0;i<m;i++){
int x = query[i][0], y = query[i][1];
int fa_x = mask[x], fa_y = mask[y];
if(fa_x!=fa_y) ans[i] = -1;
else ans[i] = dist[fa_x];
}
return ans;
}
};