🧩 题目解析
题目要求我们解码一个按照特定规则编码的字符串:
- 编码格式为:
k[encoded_string] - 表示
encoded_string要重复k次 k是正整数,且输入保证有效、无额外空格、括号匹配- 原始数据不含数字,所有数字都用于表示重复次数
示例回顾
"3[a]2[bc]" -> "aaabcbc"
"3[a2[c]]" -> "accaccacc"
"2[abc]3[cd]ef" -> "abcabccdcdcdef"
注意看第二个例子:3[a2[c]],它是一个嵌套结构:
- 先处理内层
2[c]得到cc - 再把
a和cc组成acc - 最后重复 3 次 →
accaccacc
这种嵌套结构正是使用 栈 的绝佳场景!
🔧 解题思路:用栈模拟“回退”与“合并”
我们不能简单地从左到右遍历并直接拼接,因为:
- 数字可能不止一位(如 12[abc])
- 可能存在多层嵌套(如 3[2[a]b])
所以我们要用 栈 来记录中间状态,关键在于:
当遇到
]时,说明当前一层的重复操作结束,需要将这一层的结果“合并”后压回栈中。
💡 核心逻辑拆解
让我们逐行分析代码:
var decodeString = function(s) {
const stack = [];
let n = s.length;
for (let i = 0; i < n; i++) {
if (s[i] !== ']') {
stack.push(s[i]);
} else {
// 开始处理 ']' 的情况
let str = '';
while (stack[stack.length - 1] !== '[') {
str = stack.pop() + str; // 反向拼接字符
}
stack.pop(); // 弹出 '['
let num = '';
while (stack.length > 0 &&
'0' <= stack[stack.length - 1] &&
stack[stack.length - 1] <= '9') {
num = stack.pop() + num;
}
num = parseInt(num, 10);
stack.push(str.repeat(num));
}
}
return stack.join('');
};
📌 分步详解
第一步:初始化栈
const stack = [];
我们将用栈来保存每一个未完成的子表达式。
第二步:遍历字符串
for (let i = 0; i < n; i++) {
if (s[i] !== ']') {
stack.push(s[i]);
} else {
// 处理闭合括号
}
}
只要不是 ],就直接压入栈。包括字母、数字、[ 都可以先存起来。
第三步:遇到 ],开始解码
当遇到 ] 时,我们知道前面一定有一个 [ 和一段要重复的内容。
① 提取括号内的字符串
let str = '';
while (stack[stack.length - 1] !== '[') {
str = stack.pop() + str;
}
- 从栈顶不断弹出元素,直到遇到
[ - 因为栈是先进后出,所以弹出顺序是反的,需要用
+ str来逆序拼接 - 例如:栈里是
['a', 'c', '['],弹出后变成"ca",但我们希望是"ac"
✅ 注意:这里我们是从后往前构建字符串,所以要用
str = pop() + str
② 弹出 [
stack.pop();
把 [ 也弹出去,因为它已经完成了使命。
③ 提取重复次数(k)
let num = '';
while (stack.length > 0 &&
'0' <= stack[stack.length - 1] &&
stack[stack.length - 1] <= '9') {
num = stack.pop() + num;
}
- 从栈顶继续弹出数字,直到不是数字为止
- 同样,由于弹出顺序是倒序的,所以用
+ num来保持原顺序 - 例如:栈中有
'2','1',弹出后得到'12'
⚠️ 这里必须判断
stack.length > 0,防止越界
④ 构造结果并压回栈
num = parseInt(num, 10);
stack.push(str.repeat(num));
- 把
str重复num次 - 将结果作为一个整体重新压入栈
✅ 关键点:整个重复后的字符串当作一个单元,后续可能还要被外层重复!
第四步:返回最终结果
return stack.join('');
最后栈中剩下的就是完整的解码字符串。
🎯 举个例子走一遍流程
以 s = "3[a2[c]]" 为例:
| 步骤 | 操作 | 栈状态 |
|---|---|---|
| 1 | push '3' | ['3'] |
| 2 | push '[' | ['3', '['] |
| 3 | push 'a' | ['3', '[', 'a'] |
| 4 | push '2' | ['3', '[', 'a', '2'] |
| 5 | push '[' | ['3', '[', 'a', '2', '['] |
| 6 | push 'c' | ['3', '[', 'a', '2', '[', 'c'] |
| 7 | push ']' | 触发处理 |
👉 处理第一个 ]:
- 提取
str: 弹出 'c' → str='c' - 弹出 '['
- 提取
num: 弹出 '2' → num='2' - 推入
c.repeat(2)→'cc' - 新栈:
['3', '[', 'a', 'cc']
👉 继续遍历,遇到下一个 ]:
- 提取
str: 弹出 'cc', 'a' → str='acc' - 弹出 '['
- 提取
num: 弹出 '3' → num=3 - 推入
'acc'.repeat(3)→'accaccacc'
✅ 返回结果:"accaccacc"
🚀 为什么用栈?
- 天然支持嵌套结构:每遇到一个
[,就开启一个新层级;遇到]就关闭该层。 - 方便提取局部信息:通过弹出操作可以轻松获取括号内的内容和前面的数字。
- 避免递归调用开销:虽然问题本质是递归的,但用栈可以迭代完成,效率更高。
✅ 总结
这道题的核心思想是:
用栈模拟递归过程,每次遇到
]就处理当前层的重复逻辑,并将结果作为新的“原子单位”重新压入栈中。
关键技巧回顾:
- 使用栈存储字符和数字
- 遇到
]时,反向提取括号内字符串和前导数字 - 利用
str.repeat(k)实现重复 - 所有处理完的子串都要重新压入栈,供外层使用