
本文详解如何递归生成数字字符串对应的所有合法字母编码(a=1…z=26),修复常见边界错误(如越界取两位数)、避免全局变量污染,并提供可复用、无副作用的函数式实现。
本文详解如何递归生成数字字符串对应的所有合法字母编码(a=1…z=26),修复常见边界错误(如越界取两位数)、避免全局变量污染,并提供可复用、无副作用的函数式实现。
数字字符串解码问题(也称“解码方式”或“字母映射”问题)要求将仅含数字的字符串按规则转换为字母序列:'1' → 'a', '2' → 'b', …, '26' → 'z',且每个解码必须完整覆盖原字符串,不允许遗漏或重叠。例如输入 "1123",合法解码包括 "aabc"(1-1-2-3)、"kbc"(11-2-3)、"alc"(1-12-3)、"aaw"(1-1-23)、"kw"(11-23),共 5 种。
? 关键逻辑与常见陷阱
核心在于每一步决策:从当前位置 i 开始,尝试取 1 位 或 2 位 数字构成有效字母(1–26)。但必须严格校验:
- 取 1 位:只要
i 即可(<code>str[i]存在); - 取 2 位:需同时满足
i + 2 ≤ str.length(不越界)且Number(str.slice(i, i+2)) ≤ 26(数值合法)。
原始代码中直接拼接 str[i] + str[i+1] 并使用 || '' 的做法存在严重缺陷:当 i+1 越界时,str[i+1] 为 undefined,导致 str[i] + undefined 变成类似 "3undefined" 的非法键,查表返回空字符串 '',却仍执行 i+2 的递归跳转——这会跳过末尾字符,产生非法短结果(如 "aab")。
✅ 正确实现:函数式 + 生成器(推荐)
以下为健壮、无副作用、易复用的实现:
function* decryptGenerator(str) {
if (str.length === 0) {
yield "";
return;
}
// 尝试取 1 位数字
const oneDigit = +str[0];
if (oneDigit >= 1 && oneDigit = 2) {
const twoDigits = +str.slice(0, 2);
if (twoDigits >= 10 && twoDigits [...decryptGenerator(str)];
// 使用示例
console.log(allDecodings("1123"));
// 输出: ['aabc', 'kbc', 'alc', 'aaw', 'kw']
console.log(allDecodings("12"));
// 输出: ['ab', 'l']
console.log(allDecodings("01"));
// 输出: [] ('0' 无法映射,故无合法解)
⚠️ 注意事项与设计原则
-
禁止全局状态:原始代码中
res = []是全局变量,多次调用会导致结果累积。本实现完全通过递归返回值组合结果,保证幂等性。 -
索引 vs 切片:虽可用单索引
i控制范围,但 JavaScript 字符串切片(str.slice())语义清晰、性能足够,且使函数签名更简洁(仅需str参数)。 -
'0' 的特殊处理:
'0'不能单独解码(无字母对应),也不能作为两位数的前导(如'01'、'06'非法),因此需显式排除oneDigit === 0的情况。 - 为什么只需一个索引? 因为输入是单一字符串,解码过程本质是线性扫描与分段决策,无需维护多源状态。对比“交错两个字符串”问题需双指针(因涉及两个独立序列的消费顺序),本题天然具备单维结构。
? 总结
解决此类组合枚举问题,应坚持三个准则:
① 边界先行:每次递归前严格校验输入有效性(长度、数值范围);
② 纯函数优先:避免副作用,用返回值而非外部变量收集结果;
③ 语义直观:用 slice() 明确表达子问题范围,比索引偏移更不易出错。
掌握这一模式,可快速迁移至类似问题:如爬楼梯方案数、解码方式总数(动态规划版)、括号生成等。










