我以前的题解实在太糟糕了,我决定以后尽量说人话,因为我自己复习都看不懂。
我觉得以前先讲再一股脑给代码不合理,给完代码再解释也不合理,干脆边讲边给代码吧,这样我自己才能看懂。
给你一个字符串 s
和一个整数 k
。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k
次。
在执行上述操作后,返回 包含相同字母的最长子字符串的长度。
示例 1:
输入:s = "ABAB", k = 2 输出:4 解释:用两个'A'替换为两个'B',反之亦然。
示例 2:
输入:s = "AABABBA", k = 1 输出:4 解释: 将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。 子串 "BBBB" 有最长重复字母, 答案为 4。 可能存在其他的方法来得到同样的结果。
如果翻译一下的话,可以这么说:找到一个最长子串,使得子串长度减去字串内出现次数最多的元素个数小于等于k
找字串,某一元素的出现次数,都是滑动窗口题目里也经常涉及的内容,所以会选择使用滑动窗口
滑动窗口的话考虑是否是固定大小,右指针移入,左指针移出都应该干什么。
大小是显然不固定的,我们维护的窗口大小就是最长子串的长度
int num[26];//记录字符频次的数组
memset(num, 0, sizeof(num));//清空数组
int n = s.size();//字符串长度
int left = 0, right = 0;//前后指针
int maxn = 0;//出现次数最多的字符的出现次数
右指针移入的时候做什么?自然首先是增加移入字符的频次;看一下我们翻译后的要求,出现次数最多的元素个数,如果移入的话肯定要判断是否更新
num[s[right] - 'A']++;//right移入,该字符频次增加
maxn = fmax(maxn, num[s[right] - 'A']);//判断最大频次是否应该更新
接下来的问题是左指针什么时候移动?移出时又做什么?
我们的窗口想要保证子串长度减去字串内出现次数最多的元素个数小于等于k,那我们写一下这个表达式:
right - left + 1 - maxn <= k//right-left+1是子串的长度,maxn是字符最大频次
那如果不是这种情况,左指针应该移动才对,移动显然应该将原来位置的字符频次减一
if (right - left + 1 - maxn > k) {//这是上面表达式的反情况
num[s[left] - 'A']--;//左指针位置的字符频次减一
left++;//左指针移动
}
接下来是比较关键的思想,我们原来窗口的长度已经是最长的子串了,现在出现了不符合要求的情况,我们可以将窗口大小维持原来的长度进行移动,直到重新符合要求(也就是不进入上面的if语句),或者直到字符串末尾
最后,我们返回窗口的长度(也就是right-left)(为什么是right-left等下看代码)
我们把上面已经写的整合一下
class Solution {
public:
int characterReplacement(string s, int k) {
int num[26];//记录字符频次的数组
memset(num, 0, sizeof(num));//清空数组
int n = s.size();//字符串长度
int left = 0, right = 0;//前后指针
int maxn = 0;//出现次数最多的字符的出现次数
while (right < n) {
num[s[right] - 'A']++;//right移入,该字符频次增加
maxn = fmax(maxn, num[s[right] - 'A']);//判断最大频次是否应该更新
if (right - left + 1 - maxn > k) {
num[s[left] - 'A']--;//左指针位置的字符频次减一
left++;//左指针移动
}
right++;//右指针移动
}
return right - left;//因为退出时right==n,相当于right多走了一步,所以长度是right-left
}
};
我们是怎么实现上面的维持长度移动呢?也很简单,就是每次while循环时right在最后都++,这样如果不满足题目要我们找的子串要求,会进入if语句执行left++,然后while本来就会执行一次right++,这样窗口的长度就一直维持。等到重新符合要求,不会进入if语句,只有right++,这样窗口长度又可以重新增长。