Medium
题目描述
给定一个只包含字符 ‘0’ 和 ‘1’ 的二进制字符串 s。
如果一个字符串包含相等数量的 ‘0’ 和 ‘1’,则称该字符串是平衡的。
你最多可以在 s 中任意两个字符之间执行一次交换。然后,从 s 中选择一个平衡子串。
返回一个整数,表示你可以选择的平衡子串的最大长度。
示例 1:
输入:s = "100001"
输出:4
解释:
交换 "100001"。字符串变成 "101000"。
选择子串 "1010",它是平衡的,因为它有两个 '0' 和两个 '1'。
示例 2:
输入:s = "111"
输出:0
解释:
选择不执行任何交换。
选择空子串,它是平衡的,因为它有零个 '0' 和零个 '1'。
约束条件:
1 <= s.length <= 10^5s只包含字符 ‘0’ 和 ‘1’
提示:
- 平衡子串具有相同数量的 0 和 1
- 即使经过一次交换,答案也不能超过
2 * min(cnt0, cnt1) - 只有当交换能够改善所选窗口时,交换才有用
- 寻找一个子串,其平衡性可以通过移入或移出一个不匹配字符来修复
- 跟踪前缀平衡并检查在一次交换后有效段可以扩展多远
解题思路
这道题需要在最多一次字符交换后,找到最长的平衡子串。我们可以从两个角度来思考:
不交换的情况:直接遍历所有可能的子串,找到最长的平衡子串。
交换一次的情况:关键洞察是,如果我们要通过交换来改善某个子串的平衡性,那么这个子串在交换前应该恰好"差一个字符"就能平衡。
具体思路是使用前缀平衡来优化。定义 balance[i] 为前 i 个字符中 ‘1’ 的数量减去 ‘0’ 的数量。对于区间 [i,j],如果 balance[j] - balance[i-1] = 0,则该区间是平衡的。
对于交换的情况,我们枚举每个可能的交换操作(即交换位置 i 和 j 的字符),然后检查交换后能得到的最长平衡子串。为了优化,我们可以观察到:
- 如果交换 ‘0’ 和 ‘1’,会影响它们之间所有位置的前缀平衡值
- 我们需要找到交换后能形成
balance[r] - balance[l-1] = 0的最长区间
算法步骤:
- 计算原始字符串的前缀平衡数组
- 不交换情况下找最长平衡子串
- 枚举所有可能的交换位置,计算交换后的最长平衡子串
- 返回所有情况中的最大值
代码实现
class Solution {
public:
int longestBalanced(string s) {
int n = s.length();
vector<int> balance(n + 1, 0);
// 计算前缀平衡
for (int i = 0; i < n; i++) {
balance[i + 1] = balance[i] + (s[i] == '1' ? 1 : -1);
}
int maxLen = 0;
// 不交换的情况
unordered_map<int, int> firstOccur;
firstOccur[0] = 0;
for (int i = 1; i <= n; i++) {
if (firstOccur.count(balance[i])) {
maxLen = max(maxLen, i - firstOccur[balance[i]]);
} else {
firstOccur[balance[i]] = i;
}
}
// 交换的情况
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (s[i] != s[j]) { // 只有不同字符的交换才有意义
// 交换后重新计算这段区间的影响
int diff = (s[i] == '0') ? 2 : -2;
unordered_map<int, int> newFirstOccur;
newFirstOccur[0] = 0;
int currentMax = maxLen;
for (int k = 1; k <= n; k++) {
int newBalance = balance[k];
if (k > i && k <= j) {
newBalance += diff;
}
if (newFirstOccur.count(newBalance)) {
currentMax = max(currentMax, k - newFirstOccur[newBalance]);
} else {
newFirstOccur[newBalance] = k;
}
}
maxLen = max(maxLen, currentMax);
}
}
}
return maxLen;
}
};
class Solution:
def longestBalanced(self, s: str) -> int:
n = len(s)
balance = [0] * (n + 1)
# 计算前缀平衡
for i in range(n):
balance[i + 1] = balance[i] + (1 if s[i] == '1' else -1)
max_len = 0
# 不交换的情况
first_occur = {0: 0}
for i in range(1, n + 1):
if balance[i] in first_occur:
max_len = max(max_len, i - first_occur[balance[i]])
else:
first_occur[balance[i]] = i
# 交换的情况
for i in range(n):
for j in range(i + 1, n):
if s[i] != s[j]: # 只有不同字符的交换才有意义
# 交换后重新计算这段区间的影响
diff = 2 if s[i] == '0' else -2
new_first_occur = {0: 0}
current_max = max_len
for k in range(1, n + 1):
new_balance = balance[k]
if i < k <= j:
new_balance += diff
if new_balance in new_first_occur:
current_max = max(current_max, k - new_first_occur[new_balance])
else:
new_first_occur[new_balance] = k
max_len = max(max_len, current_max)
return max_len
public class Solution {
public int LongestBalanced(string s) {
int n = s.Length;
int[] balance = new int[n + 1];
// 计算前缀平衡
for (int i = 0; i < n; i++) {
balance[i + 1] = balance[i] + (s[i] == '1' ? 1 : -1);
}
int maxLen = 0;
// 不交换的情况
Dictionary<int, int> firstOccur = new Dictionary<int, int>();
firstOccur[0] = 0;
for (int i = 1; i <= n; i++) {
if (firstOccur.ContainsKey(balance[i])) {
maxLen = Math.Max(maxLen, i - firstOccur[balance[i]]);
} else {
firstOccur[balance[i]] = i;
}
}
// 交换的情况
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (s[i] != s[j]) { // 只有不同字符的交换才有意义
// 交换后重新计算这段区间的影响
int diff = (s[i] == '0') ? 2 : -2;
Dictionary<int, int> newFirstOccur = new Dictionary<int, int>();
newFirstOccur[0] = 0;
int currentMax = maxLen;
for (int k = 1; k <= n; k++) {
int newBalance = balance[k];
if (k > i && k <= j) {
newBalance += diff;
}
if (newFirstOccur.ContainsKey(newBalance)) {
currentMax = Math.Max(currentMax, k - newFirstOccur[newBalance]);
} else {
newFirstOccur[newBalance] = k;
}
}
maxLen = Math.Max(maxLen, currentMax);
}
}
}
return maxLen;
}
}
var longestBalanced = function(s) {
const n = s.length;
const balance = new Array(n + 1).fill(0);
// 计算前缀平衡
for (let i = 0; i < n; i++) {
balance[i + 1] = balance[i] + (s[i]
复杂度分析
| 复杂度类型 | 复杂度 |
|---|---|
| 时间复杂度 | O(n³) |
| 空间复杂度 | O(n) |
注:其中 n 是字符串长度。时间复杂度主要来自于枚举所有可能的交换位置对 O(n²),以及对每种交换重新计算最长平衡子串 O(n)。空间复杂度主要用于存储前缀平衡数组和哈希表。