力扣394题:字符串解码——栈的应用详解

42 阅读4分钟

🧩 题目解析

题目要求我们解码一个按照特定规则编码的字符串:

  • 编码格式为: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
  • 再把 acc 组成 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]]" 为例:

步骤操作栈状态
1push '3'['3']
2push '['['3', '[']
3push 'a'['3', '[', 'a']
4push '2'['3', '[', 'a', '2']
5push '['['3', '[', 'a', '2', '[']
6push 'c'['3', '[', 'a', '2', '[', 'c']
7push ']'触发处理

👉 处理第一个 ]

  • 提取 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"


🚀 为什么用栈?

  1. 天然支持嵌套结构:每遇到一个 [,就开启一个新层级;遇到 ] 就关闭该层。
  2. 方便提取局部信息:通过弹出操作可以轻松获取括号内的内容和前面的数字。
  3. 避免递归调用开销:虽然问题本质是递归的,但用栈可以迭代完成,效率更高。

✅ 总结

这道题的核心思想是:

用栈模拟递归过程,每次遇到 ] 就处理当前层的重复逻辑,并将结果作为新的“原子单位”重新压入栈中。

关键技巧回顾:

  • 使用栈存储字符和数字
  • 遇到 ] 时,反向提取括号内字符串和前导数字
  • 利用 str.repeat(k) 实现重复
  • 所有处理完的子串都要重新压入栈,供外层使用