2024. 考试的最大困扰度 - 力扣(LeetCode) (leetcode-cn.com)
【考察点】前缀和+二分、滑动窗口
方法1:前缀和+二分
为什么会想到用二分做?
从题目的表述来看,在添加k次操作后,最长连续字符的结果一定位于区间 [ m , n ] [m, n] [m,n](其中, m m m表示原先字符串中连续字符的最大长度, n n n表示字符串长度)
从结果的单调性质不难想到:针对具有单调性的区间,利用二分求出结果
思路
既然已经确定利用二分的思想枚举最终解
现在的问题就在于:
如何验证解的正确性?
首先,需要进行前缀和预处理操作
简单而言,前缀和数组s[i]表示前i个字符中”T”/”F”的数量
接下来,便是遍历整个数组
并以当前的遍历点 i i i作为右端点,0为左端点,二分出可能值 x x x(即在经过不超过 k k k次操作后,可能的最长连续字符长度),则x具有的性质如下:
- x尽可能离右端点越远越好(代表连续字符更长)
- 同时需要满足以 s [ x ] + k ≥ s [ i ] s[x]+k ≥ s[i] s[x]+k≥s[i](表示中间的字符能够通过不超过 k k k次的转换变成相匹配字符,如从”T”变成”F”)
在二分结束后,最长的连续字符长度就是 i − l + 1 i - l + 1 i−l+1( i i i为当前遍历点,即固定的右端点, l l l表示遍历后距离i最远且满足不超过 k k k次操作的位置)
【需要注意的是】,对于T和F需要分别进行上述操作
实现
class Solution {
public:
bool check(int x, int k, int a[], int sum) {
if(a[x] + k >= sum) return true;
else return false;
}
int maxConsecutiveAnswers(string a, int k) {
int n = a.size();
int sum_T[n+1], sum_F[n+1];
memset(sum_T, 0, sizeof(sum_T));
memset(sum_F, 0, sizeof(sum_F));
int ans = 0;
for(int i = 1; i <= n; i++) {
// 前缀和数组
sum_T[i] = sum_T[i-1] + (a[i-1] == 'T');
sum_F[i] = sum_F[i-1] + (a[i-1] == 'F');
// 分别二分T和F
int l = 0, r = i - 1;
while(l < r) {
int mid = (l + r) >> 1;
if(check(mid, k, sum_T, sum_T[i])) r = mid;
else l = mid + 1;
}
ans = max(ans, i - l); // 需要注意的细节是,数组是从1开始的,因此这里就是(i-1)-l+1
l = 0, r = i - 1;
while(l < r) {
int mid = (l + r) >> 1;
if(check(mid, k, sum_F, sum_F[i])) r = mid;
else l = mid + 1;
}
ans = max(ans, i - l);
}
return ans;
}
};
算法复杂度分析
对于遍历一遍字符数组,复杂度是 O ( n O(n O(n)
同时,每个循环内部,需要二分从0~i的区间,复杂度是 O ( l o g n ) O(logn) O(logn)
综上,复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
方法2:滑动窗口
借助滑动窗口的思想,找到满足条件的最大值
具体而言:
- 首先,维护一个右端点,以及一个中间变量 s u m sum sum以保存从左端点到右端点中不同字符的个数
- 当 s u m > k sum>k sum>k时,说明当前维护的区间无法满足在 k k k次操作的情况下变为同一字符
- 进而,挪动左端点,并相应的修改 s u m sum sum的值,直到 s u m ≤ k sum≤k sum≤k
下面通过一个实例体会一下滑动窗口是如何实现的?
实现
class Solution {
public:
int maxConsecutiveAnswers(string a, int k) {
int n = a.size();
int ans = 0;
int t = 0;
for(int r = 0, l = 0; r < n; r++) {
t += a[r] == 'F';
while(t > k) {
if(a[l++] == 'F') t--;
}
ans = max(ans, r - l + 1);
}
t = 0;
for(int r = 0, l = 0; r < n; r++) {
t += a[r] == 'T';
while(t > k) {
if(a[l++] == 'T') t--;
}
ans = max(ans, r - l + 1);
}
return ans;
}
};
复杂度分析
只需要遍历该字符串一遍,时间算法复杂度为 O ( n ) O(n) O(n)