Medium
题目描述
给你一个长度为 n 的字符串 s ,其中仅含有 4 种字符:‘Q’、‘W’、‘E’ 和 ‘R’。
如果一个字符串的每个字符在其中都恰好出现 n / 4 次,那么该字符串就是平衡的。其中 n 是字符串的长度。
返回使 s 平衡所需替换的最小子串长度。如果 s 已经平衡,则返回 0。
示例 1:
输入:s = "QWER"
输出:0
解释:s 已经是平衡的了。
示例 2:
输入:s = "QQWE"
输出:1
解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。
示例 3:
输入:s = "QQQW"
输出:2
解释:我们可以把前面的 "QQ" 替换成 "ER"。
提示:
n == s.length4 <= n <= 10^5n是 4 的倍数s仅含有 ‘Q’、‘W’、‘E’ 和 ‘R’ 四种字符
提示:
- 使用双指针算法确保双指针外的所有字符数量都小于或等于 n/4
- 这意味着你需要统计每个字母的数量并确保数量足够
解题思路
这道题的核心思路是使用滑动窗口(双指针)来找到最小的需要替换的子串。
分析过程:
首先理解题目:我们需要让每个字符都出现 n/4 次。如果某个字符出现次数超过 n/4,那么多出来的部分必须被替换掉。
关键观察: 如果我们能找到一个窗口,使得窗口外的每个字符数量都不超过 n/4,那么我们就可以通过替换窗口内的字符来达到平衡。
算法步骤:
- 统计每个字符的出现次数,计算每个字符需要减少的数量
- 如果所有字符都不需要减少,说明已经平衡,返回 0
- 使用滑动窗口,维护窗口外每个字符的数量
- 当窗口外所有字符数量都不超过 n/4 时,当前窗口就是一个可行解
- 不断收缩左边界来寻找最小窗口
时间复杂度分析: 双指针最多遍历字符串两次,所以是 O(n)。 空间复杂度分析: 只需要常数空间存储字符计数,所以是 O(1)。
代码实现
class Solution {
public:
int balancedString(string s) {
int n = s.length();
int target = n / 4;
// 统计每个字符出现次数
unordered_map<char, int> count;
for (char c : s) {
count[c]++;
}
// 计算需要减少的字符数量
unordered_map<char, int> need;
for (char c : {'Q', 'W', 'E', 'R'}) {
need[c] = max(0, count[c] - target);
}
// 如果已经平衡,返回0
if (need['Q'] == 0 && need['W'] == 0 && need['E'] == 0 && need['R'] == 0) {
return 0;
}
int left = 0, right = 0;
int minLen = n;
unordered_map<char, int> window;
while (right < n) {
window[s[right]]++;
// 当窗口包含了足够多需要替换的字符时,尝试收缩左边界
while (window['Q'] >= need['Q'] && window['W'] >= need['W'] &&
window['E'] >= need['E'] && window['R'] >= need['R']) {
minLen = min(minLen, right - left + 1);
window[s[left]]--;
left++;
}
right++;
}
return minLen;
}
};
class Solution:
def balancedString(self, s: str) -> int:
n = len(s)
target = n // 4
# 统计每个字符出现次数
count = {}
for c in s:
count[c] = count.get(c, 0) + 1
# 计算需要减少的字符数量
need = {}
for c in 'QWER':
need[c] = max(0, count.get(c, 0) - target)
# 如果已经平衡,返回0
if all(need[c] == 0 for c in 'QWER'):
return 0
left = 0
min_len = n
window = {}
for right in range(n):
window[s[right]] = window.get(s[right], 0) + 1
# 当窗口包含了足够多需要替换的字符时,尝试收缩左边界
while all(window.get(c, 0) >= need[c] for c in 'QWER'):
min_len = min(min_len, right - left + 1)
window[s[left]] = window.get(s[left], 0) - 1
left += 1
return min_len
public class Solution {
public int BalancedString(string s) {
int n = s.Length;
int target = n / 4;
// 统计每个字符出现次数
Dictionary<char, int> count = new Dictionary<char, int>();
foreach (char c in s) {
count[c] = count.GetValueOrDefault(c, 0) + 1;
}
// 计算需要减少的字符数量
Dictionary<char, int> need = new Dictionary<char, int>();
foreach (char c in "QWER") {
need[c] = Math.Max(0, count.GetValueOrDefault(c, 0) - target);
}
// 如果已经平衡,返回0
if (need['Q'] == 0 && need['W'] == 0 && need['E'] == 0 && need['R'] == 0) {
return 0;
}
int left = 0;
int minLen = n;
Dictionary<char, int> window = new Dictionary<char, int>();
for (int right = 0; right < n; right++) {
window[s[right]] = window.GetValueOrDefault(s[right], 0) + 1;
// 当窗口包含了足够多需要替换的字符时,尝试收缩左边界
while (window.GetValueOrDefault('Q', 0) >= need['Q'] &&
window.GetValueOrDefault('W', 0) >= need['W'] &&
window.GetValueOrDefault('E', 0) >= need['E'] &&
window.GetValueOrDefault('R', 0) >= need['R']) {
minLen = Math.Min(minLen, right - left + 1);
window[s[left]] = window.GetValueOrDefault(s[left], 0) - 1;
left++;
}
}
return minLen;
}
}
var balancedString = function(s) {
const n = s.length;
const target = n / 4;
// 统计每个字符出现次数
const count = {};
for (const c of s) {
count[c] = (count[c] || 0) + 1;
}
// 计算需要减少的字符数量
const need = {};
for (const c of 'QWER') {
need[c] = Math.max(0, (count[c] || 0) - target);
}
// 如果已经平衡,返回0
if (need['Q']
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 双指针遍历字符串,每个字符最多被访问两次 |
| 空间复杂度 | O(1) | 只需要常数空间存储字符计数,因为字符集大小固定为4 |