字符串解码 图解题解
括号嵌套几层就要重复几层,从外往里替换根本下不了手——两个栈帮你记住每一层的上下文,钻进去再原路合并回来。
像打开层层嵌套的礼品盒:每遇到一个新盒子(左括号),先把手里已经拼好的东西和这层要重复的次数「存档」压入两个栈,腾空双手去拆里面的盒子;遇到右括号就把最里层的内容重复指定次数,再把存档取出来拼在后面。越深的括号越后处理,拆完再往外合并,栈的后进先出天然对应括号嵌套顺序。
这道题到底在问什么
- 输入
- s="3[ab2[cd]e]4[xy]zw"
- 输出
- "abcdcdeabcdcdeabcdcdexyxyxyxyzw"
最优解:为什么这么做
一句话答案:LeetCode 394 字符串解码的标准解法是双栈一次遍历:遇到左方括号就把攒好的倍数 k 和已拼好的前串一起压栈、然后清零重新收集,遇到右方括号就弹出两者,把括号内容重复 k 次接回前串。时间 O(maxK · n)(n 为输出规模)、空间 O(n),一遍扫完即得解码结果。
字符串解码这道题在问什么
输入形如 3[ab2[cd]e] 的编码串,规则是 k[内容] 表示把方括号里的内容重复 k 次,且括号可以任意嵌套,要求还原出展开后的字符串。真正的难点只有一个:嵌套。扫到外层的 3[ab 时里面的 2[cd] 还没解开,外层「重复 3 次」这件事必须先记下来、等内层处理完再兑现。
为什么处理嵌套括号自然想到栈
嵌套括号的闭合顺序是后进先出:最晚打开的括号最先闭合,每个右方括号要匹配的都是最近一个还没闭合的左方括号。「最近打开、最先处理」正是栈的定义,所以括号类解析几乎都以栈为骨架。
关键观察是:任何时刻真正需要动手维护的只有两样东西——当前括号层正在收集的串 cur,和正在逐位攒的倍数 k。至于外层「等会儿要重复几次、展开后接在谁后面」,属于暂时用不上的上下文,遇到左方括号时整体压栈冷冻,等对应的右方括号出现再解冻,恰好不多不少。
两个栈各存什么,压栈后为什么要清空
参考代码用两个栈配合:num_st 存各层的倍数,str_st 存进入各层之前已经拼好的前串。扫描规则四条:遇数字按 k = k * 10 + 当前位累积;遇左方括号把 k 和 cur 分别压栈,然后 cur 清空、k 归零;遇字母直接接到 cur 末尾;遇右方括号执行 cur = str_st.pop() + cur * num_st.pop()。
压栈后必须清空,因为括号内部是一段全新的子串,要从零开始收集,旧的 cur 若不清空会把外层内容错误地卷进重复。而右方括号处的拼接方向也不能反:弹出的前串在前、重复好的括号内容在后,这与它们在原串里的先后位置一致。
为什么一遍扫完结果就是对的
整个算法维持着一条不变量:任何时刻 cur 都是「当前最深那层括号内、已扫过部分的完整展开」,而栈里自底向上依次保存着更外层的半成品和倍数。每闭合一层,就把该层的展开乘上倍数、合并回上一层的半成品,不变量重新成立。扫完全串时所有括号都已闭合、两栈为空,cur 自然就是整串的展开结果,不需要任何补处理。
复杂度怎么算,哪些细节容易错
时间 O(maxK · n):每个字符可能被外层倍数放大若干倍,n 取输出长度的量级;空间 O(n),两个栈的深度随嵌套层数增长,cur 随输出增长。
两个高频翻车点:一是倍数可能是多位数,12[a] 里的 12 必须用 k = k * 10 + int(c) 累积,按一位数处理直接出错;二是想省掉一个栈行不通,倍数和前串是两类信息,闭合时要同时取回才能还原「重复几次、接到谁后面」。若偏好递归,可以在每个左方括号处递归解码子串,用调用栈替代显式栈,逻辑完全等价。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这四条规则,下面每帧都在套它。
- 4开始:当前串 cur 为空,两个栈都空。
- 5遇到数字 3:累积倍数 k = 3(记下来,等遇到 '[' 再用)。
- 6遇到 '[':把倍数 3 压进倍数栈、把当前串 "空" 压进串栈,然后清空当前串、倍数归零,开始收集括号里的内容。
- 7遇到字母 'a':直接接到当前串末尾 → "a"。
- 8遇到字母 'b':直接接到当前串末尾 → "ab"。
- 9遇到数字 2:累积倍数 k = 2(记下来,等遇到 '[' 再用)。
- 10遇到 '[':把倍数 2 压进倍数栈、把当前串 "ab" 压进串栈,然后清空当前串、倍数归零,开始收集括号里的内容。
- 11遇到字母 'c':直接接到当前串末尾 → "c"。
- 12遇到字母 'd':直接接到当前串末尾 → "cd"。
- 13遇到 ']':弹出倍数 2 与前串 "ab",把括号里的 "cd" 重复 2 次得 "cdcd",接到前串后 → 当前串 "abcdcd"。
- 14遇到字母 'e':直接接到当前串末尾 → "abcdcde"。
- 15遇到 ']':弹出倍数 3 与前串 "空",把括号里的 "abcdcde" 重复 3 次得 "abcdcdeabcdcdeabcdcde",接到前串后 → 当前串 "abcdcdeabcdcdeabcdcde"。
- 16遇到数字 4:累积倍数 k = 4(记下来,等遇到 '[' 再用)。
- 17遇到 '[':把倍数 4 压进倍数栈、把当前串 "abcdcdeabcdcdeabcdcde" 压进串栈,然后清空当前串、倍数归零,开始收集括号里的内容。
- 18遇到字母 'x':直接接到当前串末尾 → "x"。
- 19遇到字母 'y':直接接到当前串末尾 → "xy"。
- 20遇到 ']':弹出倍数 4 与前串 "abcdcdeabcdcdeabcdcde",把括号里的 "xy" 重复 4 次得 "xyxyxyxy",接到前串后 → 当前串 "abcdcdeabcdcdeabcdcdexyxyxyxy"。
- 21遇到字母 'z':直接接到当前串末尾 → "abcdcdeabcdcdeabcdcdexyxyxyxyz"。
- 22遇到字母 'w':直接接到当前串末尾 → "abcdcdeabcdcdeabcdcdexyxyxyxyzw"。
- 23扫完整串、两栈清空,当前串 "abcdcdeabcdcdeabcdcdexyxyxyxyzw" 就是解码结果。
⚠️ 容易写错的地方
✗ 错:倍数当成一位数
✓ 对:k = k*10 + 当前数字
可能是 12[a] 这种多位数
✗ 错:'[' 时忘清空 cur/k
✓ 对:压栈后 cur 清空、k 归零
括号内是一段全新的子串,要重新收集
✗ 错:']' 拼接方向反了
✓ 对:前串 prev + 当前串重复 rep 次
弹出的前串在前,重复的内容接在后面
完整代码(Python / C++ / Java)
Python
def decodeString(s):
num_st, str_st = [], []
cur, k = "", 0
for c in s:
if c.isdigit():
k = k * 10 + int(c)
elif c == "[":
num_st.append(k); str_st.append(cur)
cur, k = "", 0
elif c == "]":
cur = str_st.pop() + cur * num_st.pop()
else:
cur += c
return curC++
string decodeString(string s){
stack<int> numSt; stack<string> strSt;
string cur; int k = 0;
for(char c : s){
if(isdigit(c)) k = k*10 + (c-'0');
else if(c=='['){ numSt.push(k); strSt.push(cur); cur=""; k=0; }
else if(c==']'){
int rep = numSt.top(); numSt.pop();
string prev = strSt.top(); strSt.pop();
string t; while(rep--) t += cur;
cur = prev + t;
} else cur += c;
}
return cur;
}Java
String decodeString(String s){
Deque<Integer> numSt = new ArrayDeque<>();
Deque<String> strSt = new ArrayDeque<>();
StringBuilder cur = new StringBuilder();
int k = 0;
for(char c : s.toCharArray()){
if(Character.isDigit(c)) k = k*10 + (c-'0');
else if(c=='['){
numSt.push(k); strSt.push(cur.toString());
cur = new StringBuilder(); k = 0;
} else if(c==']'){
int rep = numSt.pop();
StringBuilder prev = new StringBuilder(strSt.pop());
for(int i=0;i<rep;i++) prev.append(cur);
cur = prev;
} else cur.append(c);
}
return cur.toString();
}复杂度
时间
O(maxK · n)
每个字符可能被外层倍数重复,n 为输出长度量级
空间
O(n)
两个栈 + 当前串,深度随嵌套层数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 字符串解码 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
能不能用递归代替栈?+
可以。每遇 '[' 递归解码括号内子串、遇 ']' 返回,递归调用栈替代显式两个栈,逻辑等价。
为什么要两个栈而不是一个?+
一个存倍数、一个存遇到 '[' 时已经拼好的前串;弹栈时两者配合还原「重复几次、接到谁后面」。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 字符串解码 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。