题目描述

✅ 394. 字符串解码

image-20260928190148882

image-20260928190148883

题意分析

编码 k[内容] 表示把方括号内的内容连续重复 k 次,再与前后其他内容依次拼接。括号内还可以包含同样的编码,所以必须先解开内层,再决定外层实际要重复的完整内容。

重复次数可能有多位,普通字母可以出现在括号内外,相邻编码段之间也不一定有分隔符。题目保证输入有效、括号匹配,数字只表示重复次数,不是要保留在输出中的普通字符。

解法:双栈解析嵌套表达式

核心思路

[!blue]

从左向右扫描时,如果没有嵌套,只需记住当前结果和重复次数。遇到内层括号后,外层还没有结束,它已经积累的前缀和重复次数都必须暂存;内层完成时,又必须先恢复最近暂存的那一层。这个后进先出的顺序正好对应栈。

用 cur 保存当前层已经解码完成的内容,用 count 累积紧接在 [ 前面的次数。数字逐位读取,所以遇到新数字要执行 count = count * 10 + digit,而不是覆盖原来的计数。

遇到 [ 时,把当前 count 压入次数栈,把当前 cur 压入前缀栈,两项共同记录这一层的恢复信息。随后将计数清零,并使用新的空缓冲区处理括号内部。这样内层追加字符时不会覆盖外层已经保存的内容。

遇到 ] 时,当前 cur 已经是这一对括号内完整的解码结果。弹出对应的次数 repeat 和外层前缀 prev,把 cur 重复追加到 prev 末尾,再令 cur = prev。新 cur 就恢复为外层目前已经完成的结果,可以继续接上同层后面的字符或编码段。

普通字母直接追加到 cur。每个右括号只与栈顶保存的最近一个左括号配对,因此嵌套层次不会混淆;所有字符处理完后,栈中各层都已合并回最外层,cur 就是完整答案。

解题步骤

  1. 创建次数栈、前缀栈,初始化空的 cur 和 count = 0。
  2. 遇到数字,将它累积到 count,支持多位重复次数。
  3. 遇到 [,把次数与外层前缀分别入栈,清零次数,并创建新的当前层缓冲区。
  4. 遇到普通字母,追加到当前层结果。
  5. 遇到 ],从两栈各弹出一项,将当前结果重复指定次数并追加到外层前缀,再恢复为新的 cur。
  6. 扫描完输入,返回 cur 对应的字符串。

代码实现

class Solution {
    public String decodeString(String s) {
        Deque<Integer> counts = new ArrayDeque<>();
        Deque<StringBuilder> prefixes = new ArrayDeque<>();
        StringBuilder cur = new StringBuilder();
        int count = 0;

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (Character.isDigit(ch)) {
                count = count * 10 + ch - '0';
            } else if (ch == '[') {
                counts.push(count);
                // 同时保存外层前缀和次数,新层使用独立缓冲。
                prefixes.push(cur);
                count = 0;
                cur = new StringBuilder();
            } else if (ch == ']') {
                int repeat = counts.pop();
                StringBuilder prev = prefixes.pop();

                for (int j = 0; j < repeat; j++) {
                    // 把内层结果按次数追加到外层前缀之后。
                    prev.append(cur);
                }

                cur = prev;
            } else {
                cur.append(ch);
            }
        }

        return cur.toString();
    }
}
func decodeString(s string) string {
    counts := make([]int, 0)
    prefixes := make([][]byte, 0)
    cur := make([]byte, 0)
    count := 0

    for i := 0; i < len(s); i++ {
        ch := s[i]
        if ch >= '0' && ch <= '9' {
            count = count*10 + int(ch-'0')
        } else if ch == '[' {
            counts = append(counts, count)
            // 同时保存外层前缀和次数,新层使用独立缓冲。
            prefixes = append(prefixes, cur)
            count = 0
            cur = make([]byte, 0)
        } else if ch == ']' {
            repeat := counts[len(counts)-1]
            counts = counts[:len(counts)-1]
            prev := prefixes[len(prefixes)-1]
            prefixes = prefixes[:len(prefixes)-1]

            for j := 0; j < repeat; j++ {
                // 把内层结果按次数追加到外层前缀之后。
                prev = append(prev, cur...)
            }
            cur = prev
        } else {
            cur = append(cur, ch)
        }
    }
    return string(cur)
}

复杂度分析

设输入长度为 n,最大嵌套深度为 d,解码后的长度为 L。

  • 时间复杂度:$O(n + dL)$。扫描输入需要 $O(n)$,字符串展开还需要复制字符;同一个最终字符在逐层合并时最多被复制 d 次,不能只按输入长度计算。
  • 空间复杂度:$O(L + d)$。两个栈最多保存 d 层信息,各层前缀、当前内容及最终结果占用与解码长度同阶的空间。

关键点总结

[!green]

  • cur 只表示当前层已完成的内容,尚未恢复的外层状态由栈保存。
  • 两个栈同步入栈和出栈,同一层的重复次数必须与它的外层前缀对应。
  • 关闭一层时,顺序始终是“外层前缀 + 内层结果重复若干次”,只有内层结果参与重复。

易错点总结

[!yellow]

  • 用当前数字覆盖 count,只能解析一位数;新数字到来时要先把已有数值乘 10。
  • 进入新层后不清零 count,后续次数会混入前一层的数值。
  • 只保存重复次数、不保存外层前缀,会丢失进入括号之前已经解析的内容。
  • 把整个“外层前缀 + 内层结果”一起重复,会错误地放大本来位于括号外的内容。
  • 新层必须使用独立缓冲区。Java 清空已入栈的同一个 StringBuilder,或 Go 用 cur = cur[:0] 复用底层数组,都可能破坏保存的外层前缀。

相似题目

题目 难度 关联与区别
385. 迷你语法分析器 中等 同样解析嵌套括号,本题把子串重复展开,原题构造嵌套整数节点。
726. 原子的数量 困难 同样用栈或递归处理分层文本,原题按括号外系数汇总原子次数。
224. 基本计算器 困难 用栈保存嵌套表达式的中间状态;本题展开带重复次数的嵌套片段,该题处理带括号的加减表达式。
227. 基本计算器 II 中等 用栈保存嵌套表达式的中间状态;本题展开带重复次数的嵌套片段,该题优先结算乘除再合并加减。
772. 基本计算器 III 困难 用栈保存嵌套表达式的中间状态;本题展开带重复次数的嵌套片段,该题同时处理括号与运算优先级。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/48855635
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!