2026-03-01:移除K-平衡子字符串。用go语言,给定一个只含左右括号的字符串 s 和一个正整数 k。
把恰好由 k 个连续左括号紧跟 k 个连续右括号组成的片段称为 k-平衡串(例如 k=3 时为 "((()))")。你需要反复进行如下操作:在当前字符串中选出若干互不重叠的 k-平衡子串,一次性将它们删掉,然后把剩下的部分重新拼接成新的字符串。不断重复这一过程,直到再也找不到任何 k-平衡子串为止。
注意这里的“子串”指的是原字符串中的连续片段。因为在选择删除的过程中可能存在不同的不重叠选择,最终可能得到多种不同的结果。返回所有可能的最终字符串。
2 <= s.length <= 100000。
s 仅由 '(' 和 ')' 组成。
1 <= k <= s.length / 2。
输入: s = "((()))()()()", k = 3
输出: "()()()"
解释:
k-平衡子串是 "((()))"
| 步骤 | 当前 s | k-平衡 | 结果 s |
|---|---|---|---|
| 1 | ((()))()()() | ((()))()()() | ()()() |
| 2 | ()()() | - | ()()() |
因此,最终字符串是 "()()()"。
题目来自力扣3703。
一、代码执行的详细分步过程
我们以输入 s = "((()))()()()"、k = 3 为例,结合代码逻辑拆解每一步执行过程:
步骤1:初始化核心数据结构
代码中定义了 pair 结构体(保存「字符 + 连续出现次数」),并初始化一个栈 st(用于追踪连续括号的状态),最终结果 ans 初始化为空字节切片。
步骤2:遍历字符串的每个字符(逐个处理括号)
遍历顺序为:(、(、(、)、)、)、(、)、(、)、(、),逐个字符处理如下:
处理第1个字符 (:
- 栈为空,直接向栈中添加
pair{b: '(', cnt: 1},此时栈内容:[{ '(', 1 }]。 - 字符不是
),无需触发「k-平衡串删除逻辑」。
处理第2个字符 (:
- 栈顶元素的字符是
(,与当前字符相同,栈顶元素的cnt加1 →cnt = 2,栈内容:[{ '(', 2 }]。 - 字符不是
),无需触发删除逻辑。
处理第3个字符 (:
- 栈顶元素的字符是
(,与当前字符相同,栈顶元素的cnt加1 →cnt = 3,栈内容:[{ '(', 3 }]。 - 字符不是
),无需触发删除逻辑。
处理第4个字符 ):
- 栈顶元素的字符是
(,与当前字符不同,向栈中添加pair{b: ')', cnt: 1},此时栈内容:[{ '(', 3 }, { ')', 1 }]。 - 字符是
),检查删除条件:- 栈长度 > 1(满足,长度为2);
- 栈顶(
))的cnt = 1≠ 3(不满足),因此不执行删除。
处理第5个字符 ):
- 栈顶元素的字符是
),与当前字符相同,栈顶元素的cnt加1 →cnt = 2,栈内容:[{ '(', 3 }, { ')', 2 }]。 - 字符是
),检查删除条件:- 栈顶(
))的cnt = 2≠ 3(不满足),不执行删除。
- 栈顶(
处理第6个字符 ):
- 栈顶元素的字符是
),与当前字符相同,栈顶元素的cnt加1 →cnt = 3,栈内容:[{ '(', 3 }, { ')', 3 }]。 - 字符是
),检查删除条件:- 栈长度 > 1(满足);
- 栈顶(
))的cnt = 3(等于k,满足); - 栈顶前一个元素(
()的cnt = 3(≥k,满足)。
- 执行删除逻辑:
- 先移除栈顶的
)元素 → 栈内容变为[{ '(', 3 }]; - 栈顶(
()的cnt减去k →3 - 3 = 0; - 因
cnt = 0,移除该(元素 → 栈变为空。
- 先移除栈顶的
处理第7个字符 (:
- 栈为空,添加
pair{b: '(', cnt: 1},栈内容:[{ '(', 1 }]。 - 字符不是
),无需删除。
处理第8个字符 ):
- 栈顶是
(,与当前字符不同,添加pair{b: ')', cnt: 1},栈内容:[{ '(', 1 }, { ')', 1 }]。 - 字符是
),检查删除条件:- 栈顶(
))的cnt = 1≠ 3(不满足),不删除。
- 栈顶(
处理第9个字符 (:
- 栈顶是
),与当前字符不同,添加pair{b: '(', cnt: 1},栈内容:[{ '(', 1 }, { ')', 1 }, { '(', 1 }]。 - 字符不是
),无需删除。
处理第10个字符 ):
- 栈顶是
(,与当前字符不同,添加pair{b: ')', cnt: 1},栈内容:[{ '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }]。 - 字符是
),检查删除条件:- 栈顶(
))的cnt = 1≠ 3(不满足),不删除。
- 栈顶(
处理第11个字符 (:
- 栈顶是
),与当前字符不同,添加pair{b: '(', cnt: 1},栈内容:[{ '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }, { '(', 1 }]。 - 字符不是
),无需删除。
处理第12个字符 ):
- 栈顶是
(,与当前字符不同,添加pair{b: ')', cnt: 1},栈内容:[{ '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }]。 - 字符是
),检查删除条件:- 栈顶(
))的cnt = 1≠ 3(不满足),不删除。
- 栈顶(
步骤3:遍历结束后,拼接最终字符串
遍历完成后,栈中剩余的元素是:
[{ '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }, { '(', 1 }, { ')', 1 }]
代码遍历栈中每个 pair,将「字符重复对应次数」拼接成字符串:
- 第1个pair:
(重复1次 → "("; - 第2个pair:
)重复1次 → ")"; - 第3个pair:
(重复1次 → "("; - 第4个pair:
)重复1次 → ")"; - 第5个pair:
(重复1次 → "("; - 第6个pair:
)重复1次 → ")";
最终拼接结果为 "()()()",与题目预期输出一致。
步骤4:关键逻辑补充(反复删除的隐含实现)
代码通过「栈实时检测并删除k-平衡串」的方式,天然实现了「反复删除直到无k-平衡串」的逻辑:
- 每次处理字符时,只要形成k-平衡串就立即删除,删除后剩余字符的连续状态会被栈继续追踪;
- 后续字符与删除后的剩余字符拼接后,若再次形成k-平衡串,会在遍历到对应字符时再次触发删除(本例中后续无k-平衡串,因此一次遍历即可完成)。
二、时间复杂度与空间复杂度分析
1. 时间复杂度:O(n)
- 核心操作是「遍历字符串的每个字符」:每个字符仅入栈1次、出栈1次(若触发删除),单个字符的入栈/出栈/计数更新都是O(1)操作;
- 最终拼接结果时,遍历栈的每个元素,栈的总元素数不超过原字符串长度n;
- 所有操作的总次数与字符串长度n成正比,因此时间复杂度为O(n)(n为字符串s的长度)。
2. 额外空间复杂度:O(n)
- 主要额外空间是「栈
st」:最坏情况下(无任何k-平衡串可删除),栈需要存储所有字符的pair结构,空间占用与n成正比; - 结果切片
ans最终存储的是处理后的字符串,属于输出空间,一般不计入「额外空间复杂度」; - 因此额外空间复杂度为O(n)(n为字符串s的长度)。
总结
- 核心过程:通过栈追踪连续括号的「字符+次数」,遍历字符串时实时检测k-平衡串,一旦满足条件就删除对应片段,最终拼接栈中剩余字符得到结果;
- 时间复杂度:O(n)(n为字符串长度,每个字符仅被处理常数次);
- 空间复杂度:O(n)(栈的最大空间不超过字符串长度)。
Go完整代码如下:
package main
import (
"fmt"
"strings"
)
func removeSubstring(s string, k int) string {
type pair struct {
b rune
cnt int
}
st := []pair{} // 栈中保存 pair{字符, 连续出现次数}
for _, b := range s {
if len(st) > 0 && st[len(st)-1].b == b {
st[len(st)-1].cnt++ // 连续相同括号个数 +1
} else {
st = append(st, pair{b, 1}) // 新的括号
}
// 栈顶的 k 个右括号与栈顶下面的 k 个左括号抵消
if b == ')' && len(st) > 1 && st[len(st)-1].cnt == k && st[len(st)-2].cnt >= k {
st = st[:len(st)-1]
st[len(st)-1].cnt -= k
if st[len(st)-1].cnt == 0 {
st = st[:len(st)-1]
}
}
}
ans := []byte{}
for _, p := range st {
ans = append(ans, strings.Repeat(string(p.b), p.cnt)...)
}
return string(ans)
}
func main() {
s := "((()))()()()"
k := 3
result := removeSubstring(s, k)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def remove_substring(s: str, k: int) -> str:
st = [] # 栈中保存 [字符, 连续出现次数]
for b in s:
if st and st[-1][0] == b:
st[-1][1] += 1 # 连续相同字符个数 +1
else:
st.append([b, 1]) # 新的字符
# 栈顶的 k 个右括号与栈顶下面的 k 个左括号抵消
if b == ')' and len(st) > 1 and st[-1][1] == k and st[-2][1] >= k:
st.pop() # 移除栈顶的 k 个右括号
st[-1][1] -= k # 左括号减少 k 个
if st[-1][1] == 0:
st.pop() # 如果左括号数量变为 0,也移除
# 构建结果字符串
result = []
for char, count in st:
result.append(char * count)
return ''.join(result)
def main():
s = "((()))()()()"
k = 3
result = remove_substring(s, k)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <string>
#include <vector>
struct Pair {
char b; // 字符
int cnt; // 连续出现次数
};
std::string removeSubstring(std::string s, int k) {
std::vector<Pair> st; // 栈中保存 Pair{字符, 连续出现次数}
for (char b : s) {
if (!st.empty() && st.back().b == b) {
st.back().cnt++; // 连续相同字符个数 +1
} else {
st.push_back({b, 1}); // 新的字符
}
// 栈顶的 k 个右括号与栈顶下面的 k 个左括号抵消
if (b == ')' && st.size() > 1 && st.back().cnt == k && st[st.size() - 2].cnt >= k) {
st.pop_back(); // 移除栈顶的 k 个右括号
st.back().cnt -= k; // 左括号减少 k 个
if (st.back().cnt == 0) {
st.pop_back(); // 如果左括号数量变为 0,也移除
}
}
}
// 构建结果字符串
std::string result;
for (const auto& p : st) {
result.append(p.cnt, p.b); // 添加 p.cnt 个 p.b 字符
}
return result;
}
int main() {
std::string s = "((()))()()()";
int k = 3;
std::string result = removeSubstring(s, k);
std::cout << result << std::endl;
return 0;
}