394. 字符串解码 (Medium)

专题归类: 06-栈与堆 LeetCode 链接: https://leetcode.cn/problems/decode-string/


在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode

题目描述

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为:k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k,例如不会出现像 3a2[4] 的输入。

示例 1:

输入:s = "3[a]2[bc]"
输出:"aaabcbc"

示例 2:

输入:s = "3[a2[c]]"
输出:"accaccacc"

示例 3:

输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"

示例 4:

输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"

题目详细分析

  • 数据范围: 1 ≤ s.length ≤ 30,但解码后的字符串长度可能远大于原始输入(因为重复展开)。最大长度可能非常大(指数级),但题目中 k 范围应该有限。
  • 输入输出特征: 输入只包含字母、数字和 []。数字只出现在 [ 之前,表示重复次数。字母可以是小写或大写?题目说字母,实际是小写英文字母。
  • 边界条件:
    • 无嵌套:3[a] 最简单。
    • 嵌套多层:3[a2[c]b](a 后面嵌套了 2[c])。
    • 数字可能多位:10[a] 表示 “a” 重复 10 次。
    • 数字可能为 1:1[a] → “a”,等价于无编码。
    • 开头或结尾有无编码的字母:abc3[d]ef
  • 核心约束: 嵌套结构是核心难点,需要后遇到的 [...] 先解码(LIFO 特性),因此使用栈来解决。
  • 隐藏条件: 括号内的内容可能既包含字母也包含嵌套的 k[...]。解码时要从最内层开始向外处理。

小白版直白理解

就像在做 俄罗斯套娃:一层套一层,每层都有个数字告诉你这层娃娃要复制几份。

最里层的 2[c] 表示 “cc”,往外一层 3[a + 里层结果] = 3[acc] = “accaccacc”。

从里到外的顺序正好和栈的 后进先出 一致:最里层的 ] 最先遇到,但我们需要先处理它。

想象你在剥洋葱:

  • 看到 [ 时,把当前已经处理的字符串和数字存起来,然后清零开始处理内层
  • 看到 ] 时,把内层结果弹出来,乘以数字,拼接到上一层的结果后面

解题思路

思路一:辅助栈法(推荐)

核心思想: 用两个变量 cur_strcur_num 分别追踪当前正在构建的字符串和当前累积的数字。遇到 [ 时,将当前状态入栈并重置;遇到 ] 时,从栈中弹出状态并拼接结果。

def decodeString(s):
    stack = []          # 存储 (之前的字符串, 重复次数)
    cur_str = ''        # 当前正在构建的字符串
    cur_num = 0         # 当前累积的数字
 
    for ch in s:
        if ch.isdigit():
            cur_num = cur_num * 10 + int(ch)   # 处理多位数
        elif ch == '[':
            stack.append((cur_str, cur_num))   # 保存当前状态
            cur_str = ''    # 重置,开始处理内层
            cur_num = 0     # 重置数字
        elif ch == ']':
            prev_str, num = stack.pop()        # 取出外层状态
            cur_str = prev_str + cur_str * num # 拼接:外层 + 内层×次数
        else:  # 字母
            cur_str += ch
 
    return cur_str

复杂度: 时间 O(n)(n 为解码后字符串长度),空间 O(n)

思路二:双栈法(数字栈 + 字符串栈)

将数字和字符串分别存储在独立的栈中,逻辑更清晰但代码稍长。

def decodeString(s):
    num_stack = []     # 数字栈
    str_stack = []     # 字符串栈
    cur_num = 0
    cur_str = ''
 
    for ch in s:
        if ch.isdigit():
            cur_num = cur_num * 10 + int(ch)
        elif ch == '[':
            num_stack.append(cur_num)
            str_stack.append(cur_str)
            cur_num = 0
            cur_str = ''
        elif ch == ']':
            cur_str = str_stack.pop() + cur_str * num_stack.pop()
        else:
            cur_str += ch
 
    return cur_str

思路三:递归解法

k[...] 具有递归结构:一个编码字符串可以递归地解码。遇到 [ 时递归进入内层,遇到 ] 时返回结果。

def decodeString(s):
    def dfs(i):
        res = ''
        num = 0
        while i < len(s):
            if s[i].isdigit():
                num = num * 10 + int(s[i])
            elif s[i] == '[':
                inner_str, i = dfs(i + 1)   # 递归处理内层
                res += inner_str * num
                num = 0
            elif s[i] == ']':
                return res, i
            else:  # 字母
                res += s[i]
            i += 1
        return res, i
 
    result, _ = dfs(0)
    return result

复杂度: 时间 O(n),空间 O(n)(递归栈深度等于嵌套层数)


易错点

  • 多位数处理: 数字可能不止一位(如 12[a]),需要用 cur_num = cur_num * 10 + int(ch) 累积,而不是直接赋值。忘记处理多位数是最常见的 bug。
  • 嵌套重置: 遇到 [ 时需要重置 cur_strcur_num,否则内层会错误地继承外层的状态。
  • 拼接顺序: prev_str + cur_str * num — 外层的字符串应该在前,内层重复后的字符串在后。顺序搞反会导致结果错误(如 3[a2[c]] 会得到 “ca” × 3 而不是 “acc” × 3)。
  • 栈的初始化: 一开始栈是空的,遇到数字后累积,直到 [ 才入栈。注意栈中的状态是 入栈时的值,不是出栈时再计算的。
  • 字符串末尾无括号:abc3[cd]xyz,最后的 “xyz” 直接拼接到结果即可。

框架提炼

编码字符串解码模板(栈处理嵌套):

def decode_nested_string(s):
    """通用模板:处理 k[pattern] 嵌套解码"""
    stack = []
    cur_str = ''
    cur_num = 0
    
    for ch in s:
        if ch.isdigit():
            cur_num = cur_num * 10 + int(ch)
        elif ch == '[':
            stack.append((cur_str, cur_num))
            cur_str = ''
            cur_num = 0
        elif ch == ']':
            prev_str, num = stack.pop()
            cur_str = prev_str + cur_str * num
        else:
            cur_str += ch
    
    return cur_str

这种「遇到分隔符入栈/出栈」的模式还可以解决:

  • 表达式求值(遇到 ) 出栈计算)
  • HTML 标签解析(遇到 </tag> 出栈检查)
  • JSON 解析器

关联题目

  • 20-有效的括号 — 括号匹配是 394 的基础版。394 在括号匹配的基础上增加了数字控制和字符串拼接逻辑。
  • 739-每日温度 — 栈在顺序处理场景中的另一种经典应用(单调栈 vs 嵌套处理栈)。
  • 224-基本计算器 — 表达式求值,同样使用栈处理括号嵌套和运算符优先级,比 394 更复杂(需要处理加减号和运算符优先级)。