有一个只含有 'Q', 'W', 'E', 'R'
四种字符,且长度为 n
的字符串。
假如在该字符串中,这四个字符都恰好出现 n/4
次,那么它就是一个「平衡字符串」。
给你一个这样的字符串 s
,请通过「替换一个子串」的方式,使原字符串 s
变成一个「平衡字符串」。
你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。
请返回待替换子串的最小可能长度。
如果原字符串自身就是一个平衡字符串,则返回 0
。
示例 1:
输入:s = "QWER" 输出:0 解释:s 已经是平衡的了。
示例 2:
输入:s = "QQWE" 输出:1 解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。
示例 3:
输入:s = "QQQW" 输出:2 解释:我们可以把前面的 "QQ" 替换成 "ER"。
示例 4:
输入:s = "QQQQ" 输出:3 解释:我们可以替换后 3 个 'Q',使 s = "QWER"。
提示:
1 <= s.length <= 10^5
s.length
是4
的倍数s
中只含有'Q'
,'W'
,'E'
,'R'
四种字符
提示 1
Use 2-pointers algorithm to make sure all amount of characters outside the 2 pointers are smaller or equal to n/4.
提示 2
That means you need to count the amount of each letter and make sure the amount is enough.
解法:滑动窗口
题目的要求是找到一个「待替换子串」,通过替换这个子串,使得原字符串s变成一个「平衡字符串」。
也就是说原字符串可以看作两个部分:「待替换子串」 + 「不替换的内容」
根据题意,如果在待替换子串之外的任意字符的出现次数超过 m = n / 4 ,那么无论怎么替换,都无法使这个字符在整个字符串中的出现次数为 m。
也就是说,如果在待替换子串之外的任意字符的出现次数都不超过 m,那么可以通过替换,使 s 为平衡字符串,即每个字符的出现次数均为 m。
因此我们的目标就是,找到最短的「待替换子串」,使得「不替换的内容」中四个字符的出现次数小于等于m。
那么如何去搜索最短的「待替换子串」?
当「不替换的内容」(窗口外的串)中四个字符的出现次数小于等于 m 时,此时「待替换子串」(窗口内的子串)为合法子串。
- 如果当前得到的子串是一个合法子串,那么left右移缩短字符串;如果left已经到达字符串尾部,说明没有缩小的空间,那么就结束搜索。
- 如果当前得到的子串不是合法子串,right右移延伸字符串;如果right已经到达字符串尾部,说明没有延伸的空间,那么就结束搜索。
因为我们在搜索过程中要时刻判断 (窗口外的串) 中四个字符的出现次数是否小于等于 m ,因此我们必须先遍历一遍整个字符串,得到字符的总共出现次数,然后在滑动窗口的移动过程中,动态的增加或减少对应字符出现的次数,来维护(窗口外的串)中四个字符的出现次数。
class Solution {
public int balancedString(String s) {
int n = s.length();
int m = n / 4;
// 存储窗口外的四种字符各自的数量
int[] windowOut = new int[4];
for (int i = 0; i < n; i++) {
switch (s.charAt(i)) {
case 'Q':
windowOut[0]++;
break;
case 'W':
windowOut[1]++;
break;
case 'E':
windowOut[2]++;
break;
case 'R':
windowOut[3]++;
break;
}
}
// 窗口外的四种字符各自的数量都恰好等于m,它已经是一个「平衡字符串」了
if (windowOut[0] == m && windowOut[1] == m && windowOut[2] == m && windowOut[3] == m) {
return 0;
}
int ans = Integer.MAX_VALUE;
int left = 0;
int right = 0;
while (right < n) {
char c = s.charAt(right);
switch (c) {
case 'Q':
windowOut[0]--;
break;
case 'W':
windowOut[1]--;
break;
case 'E':
windowOut[2]--;
break;
case 'R':
windowOut[3]--;
break;
}
// 窗口外的任何一种字符数量都不超过m,此时窗口内子串(代替换子串)合法,尝试收缩左边界
while (windowOut[0] <= m && windowOut[1] <= m && windowOut[2] <= m && windowOut[3] <= m) {
ans = Math.min(ans, right - left + 1);
char delete = s.charAt(left);
left++;
switch (delete) {
case 'Q':
windowOut[0]++;
break;
case 'W':
windowOut[1]++;
break;
case 'E':
windowOut[2]++;
break;
case 'R':
windowOut[3]++;
break;
}
}
// 窗口外的任何一种字符数量超过m,此时窗口内子串(代替换子串)不合法,扩大右边界
right++;
}
return ans;
}
}
复杂度分析
- 时间复杂度:O(n),n 是 字符串 s 的长度。
- 空间复杂度:O(1)。