【LeetCode 0202】424.替换后的最长重复字符(双指针)
题目描述
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度
注意:字符串长度 和 k 不会超过 104。
示例 1:
输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。
示例 2:
输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-repeating-character-replacement
参考代码(Java)-简洁版
public class Solution {
public int characterReplacement(String s, int k) {
int len = s.length();
if (len < 2) {
return len;
}
char[] charArray = s.toCharArray();
int left = 0;
int right = 0;
int[] freq = new int[26];//freq数组统计 当前子串 中每个字符出现次数
int maxCount = 0;//当前freq中的最大值
int res = 0;
while (right < len){
freq[charArray[right] - 'A']++;
maxCount = Math.max(maxCount, freq[charArray[right] - 'A']);//更新maxCount
right++;
if (right - left > maxCount + k){
// 说明此时 k 不够用
// 把其它不是最多出现的字符替换以后,都不能填满这个滑动的窗口,这个时候须要考虑左边界向右移动
// 移出滑动窗口的时候,频数数组须要相应地做减法
freq[charArray[left] - 'A']--;
left++;
}
res = Math.max(res, right - left);
}
return res;
}
}
参考代码(Java)-注释版
public class Solution {
public int characterReplacement(String s, int k) {
//========================================第一部分:特殊情况讨论√=========================================
int len = s.length();
if (len < 2) {
//字符串长度为0或1时候,直接返回
return len;
}
//========================================第二部分:必要变量定义√=========================================
char[] charArray = s.toCharArray();//字符串s -> 字符数组
int left = 0;
int right = 0;
// 技巧(左闭右开):[left, right) 代表的子串长度即 right-left
int[] freq = new int[26];//freq数组统计当前子串中每个字符出现次数
int maxCount = 0;//当前子串,freq数组里的最大值
int res = 0;//result记录最长字串长度
//========================================第三部分:指针滑动过程√=========================================
while (right < len){
//右边界向右移动
freq[charArray[right] - 'A']++;
maxCount = Math.max(maxCount, freq[charArray[right] - 'A']);//每次循环 右边界移动 -> 子串变化 -> maxCount更新
right++;
if (right - left > maxCount + k){
// 说明此时 k 不够用
// 把其它不是最多出现的字符替换以后,都不能填满这个滑动的窗口
//这个时候须要考虑左边界向右移动
// 移出滑动窗口的时候,频数数组须要相应地做减法
freq[charArray[left] - 'A']--;
left++;
}//end of if
res = Math.max(res, right - left);//res记录整个过程中动态变化着的 right-left 最大值
}//end of while
return res;
}
}
*代码技巧
- 技巧一:Java方法 字符串s -> 字符数组
char[] charArray = s.toCharArray();
- 技巧二:字符串长度技巧(左闭右开)
[left, right) 代表的子串长度即 right-left
- 技巧三:字符数组统计频数
- freq[ charArray[right] - ‘A’ ] ++ ;
算法理解(双指针)
这里建议优先顺着链接去看视频,很清楚!
- 首先,移动右边界,找到一个满足题意的最长字串(k次替换后),即 纳入下一字符不能满足时停下。
- 然后,左边界只须要向右移动一格以后,右边界就又可以开始向右移动了,继续尝试找到更长的目标子串;
- 替换后的最长重复子串就产生在右边界、左边界交替向右移动的过程中。
复杂度分析
时间复杂度:O(N),这里 N 是输入字符串 S 的长度;
空间复杂度:O(A),这里 A 是输入字符串 S 出现的字符 ASCII 值的范围
细节思考
1.证明:如果长度为 L 的子串不符合题目的要求,那么左边界固定,长度更长的子串也不符合题目的要求。
答:
两个前提:
假设长度为 L 的子串,出现最多的字符为 A,出现x次;其余字符均为 B,出现y次。
由字符 A 出现次数最多,可知 x ≥ y。
又由于长度为 L 的子串不符合题目的要求,可知 y > k。
起点固定的情况下,考虑更长的子串:
- 如果接下来看到的字符都是 A,依然须要考虑把之前看到的 B 全部替换成为 A,由于y>k,这是不能做到的;
- 如果接下来看到的字符不是 A,
若出现最多次字符不变依然为A,依然须要考虑把之前看到的 B 还有新字符全部替换成为 A,由于y>k,所以y+1>k这是不能做到的;
若出现最多次字符变更,那么须要考虑把之前看到的 A 全部替换成为新的频数最多的字符,由于x≥y>k,这也是不能做到的。
2. maxCount 在内层循环「左边界向右移动一个位置」的过程中,没有维护它的定义,结论是否正确?
答:
结论依然正确。
感性理解: 所求最长长度即为maxCount + k,如同记录一样不断被刷新。A创下的记录,下一个B可能达不到,可能打破,但我们只关注记录,如果B打破记录,那么一定有maxCount(B) > maxCount(A),无需维护。
理性推理:「左边界向右移动一个位置」的时候,maxCount 或者不变,或者减 1。
- 不变:例如 s = [AAABBB]、k = 2,左边界 A 移除以后,窗口内字符出现次数不变,依然为 3;
- 变小:由于 我们要找的只是最长替换 k 次以后重复子串的长度。接下来我们继续让右边界向右移动一格,有两种情况:
① 右边界如果读到了刚才移出左边界的字符,恰好 maxCount 的值被正确维护;
② 右边界如果读到了不是刚才移出左边界的字符,新的子串要想在符合题意的条件下变得更长,maxCount 一定要比之前的值还要更多,因此不会错过更优的解。
3. 内层循环里的 if 能不能改成 while?
答:可以但没有必要。理由依然是:我们只关心最长替换 k 次以后重复子串的长度。
正是因为多读了一个字符,使得 right - left > maxCount + k 成立;
在 left++ 以后,由于可以不维护 maxCount 的定义,right - left > maxCount + k 不成立。因此 if 里面的代码块只会被执行一次。
4. 可以不用一直用 res 记录滑动窗口的最大长度,最后返回 right - left 即可。
答:依然是 我们只关心最长替换 k 次以后重复子串的长度,并且 maxCount 只会增加不会减少。在退出内层 if 语句的时候,区间 [left, right) 不一定是符合要求的子串,但是子串的长度一定等于题目要求的替换 k 次以后字符全都相等的最长子串(maxCount 的值不会变小,所以它会一直撑着滑动窗口的长度直到 right 遍历到字符串的末尾)。这一点如果很难理解的话,我们建议大家使用小测试数据、跟踪代码进行理解。
作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-repeating-character-replacement/solution/ti-huan-hou-de-zui-chang-zhong-fu-zi-fu-eaacp/
来源:力扣(LeetCode)