题目描述

:::fold-green 相关原题

LeetCode 原题: ✅ 91. 解码方法

:::

给定非空数字字符串,按 1→A、2→B、…、26→Z 解码,返回所有解码字符串。

0 不能单独解码,两位编码不能有前导零。结果顺序不限。

示例 1:

输入: s = "121"
输出: ["ABA","AU","LA"]
解释: 分别对应 1/2/1、1/21、12/1 三种合法分割。

示例 2:

输入: s = "10"
输出: ["J"]
解释: 10 可以整体映射为 J,0 不能单独解码。

示例 3:

输入: s = "06"
输出: []
解释: 06 含前导零,且首位 0 不能单独解码。

提示:

  • 输入为非空数字字符串。
  • 编码范围为 1…26。
  • 0 不能单独解码,两位编码不能有前导零。
  • 输出顺序不限。

题意分析

每个字母编码只有一位或两位,所以从当前数字位置出发,所有合法答案都可由这两种长度的选择产生。题目要求具体字符串,需保留递归路径,而不能只用计数状态返回数量。

解法:按一位或两位数字回溯全部解码

核心思路

[!blue]

i 表示下一个尚未消费的数字位置,path 是此前分段得到的字母串。到达字符串末尾时说明全部数字均被合法覆盖,保存当前字符串;若当前数字是 0,则没有任何合法编码能从这里开始,立即终止该分支。

非零时总可尝试一位编码;若还有下一位,再组成两位数,只有不超过 26 才递归。因为已排除首位 0,两位数自然至少为 10,既不会接受前导零,也能把 10、20 作为整体处理。

每次递归前记录路径长度,返回后恢复,再尝试另一分支。每种合法分段被枚举一次,且字母与编码一一对应,不需要额外结果去重。

解题步骤

  1. 到字符串末尾时保存当前字母路径。
  2. 当前位置为 0 时停止该分支,否则先尝试一位解码。
  3. 再尝试合法的两位编码,每次递归返回后恢复路径长度。

代码实现

class Solution {
    public List<String> decodeAll(String s) {
        List<String> out = new ArrayList<>();

        if (!s.isEmpty()) {
            dfs(s, 0, new StringBuilder(), out);
        }

        return out;
    }

    private void dfs(String s, int i, StringBuilder path, List<String> out) {
        if (i == s.length()) {
            out.add(path.toString());

            return;
        }

        if (s.charAt(i) == '0') {
            return;
        }

        int size = path.length();

        path.append((char) ('A' + s.charAt(i) - '1'));
        dfs(s, i + 1, path, out);
        path.setLength(size);

        if (i + 1 < s.length()) {
            int value = (s.charAt(i) - '0') * 10 + s.charAt(i + 1) - '0';

            if (value <= 26) {
                path.append((char) ('A' + value - 1));
                dfs(s, i + 2, path, out);
                path.setLength(size);
            }
        }
    }
}
func decodeAll(s string) []string {
    out := []string{}
    path := []byte{}
    var dfs func(int)
    dfs = func(i int) {
        if i == len(s) {
            out = append(out, string(path))
            return
        }
        if s[i] == '0' {
            return
        }
        size := len(path)
        path = append(path, 'A'+s[i]-'1')
        dfs(i + 1)
        path = path[:size]
        if i+1 < len(s) {
            value := int(s[i]-'0')*10 + int(s[i+1]-'0')
            if value <= 26 {
                path = append(path, byte('A'+value-1))
                dfs(i + 2)
                path = path[:size]
            }
        }
    }
    if len(s) > 0 {
        dfs(0)
    }
    return out
}

复杂度分析

  • 时间复杂度:最坏 $O(n\cdot 2^n)$。
  • 空间复杂度:递归与路径空间 $O(n)$,结果空间另计。

关键点总结

[!green]

转移是否合法与计数题一致;枚举需要维护具体路径,输出规模可能指数增长。

易错点总结

[!yellow]

每次返回前恢复路径长度;计数动态规划不能直接代替方案枚举。

相似题目

题目 难度 关联与区别
91. 解码方法 中等 复用 1 到 26 的合法转移,但本题要保存字母路径并枚举全部结果,不能只返回计数。
93. 复原 IP 地址 中等 同样枚举数字片段并处理前导零,原题固定四段且限制到 255,本题每段一位或两位。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/87950554
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!