LeetCode 17. 电话号码的字母组合
题目描述


题意分析
输入一个只含数字
2到9的字符串,每一位数字对应电话按键上的一组字母。必须按数字的原顺序,为每个位置选一个对应字母,返回全部可能的字符串,结果顺序不限。每个结果的长度与数字串相同;重复数字的不同位置独立选择,字母也可以重复使用。数字
7、9各对应四个字母,其余对应三个。现有实现也保留空输入返回空列表的处理。
解法:回溯逐位选择字母
核心思路
[!blue]
每个数字位置提供一组候选字母,完整结果就是每组各取一个。按位置递归即可:进入
backtrack(index)时,路径长度恰好为index,前面的字母已经分别对应数字串的前index个位置;当前层只枚举当前数字的映射字母。选定一个字母后把它追加到路径,递归处理下一个位置。返回后删掉刚添加的末尾字母,再尝试本位置的其他候选。这样兄弟分支共享路径容器,却不会继承彼此的选择。
当
index等于数字串长度时,所有位置都已选好,把当前路径转成独立字符串保存。Java 的StringBuilder和 Go 的字节切片随后还会修改,因此答案保存的是字符串快照。每个答案唯一对应一串逐位选择,所有候选都会被枚举,不需要额外去重。字母是否在其他位置用过也不影响当前选择,所以不需要
used数组。若输入为空,应在递归前直接返回,避免把没有字母的路径当作一个组合。
解题步骤
- 空输入直接返回空列表;为数字下标准备完整字母映射。
- 从
index = 0和空路径开始回溯。index到达数字串长度时,将路径复制成字符串加入结果,然后返回。- 否则读取当前数字对应的字母组,逐个执行“追加字母、递归下一位、删除末尾字母”。
- 全部候选遍历完后,返回收集到的组合列表。
代码实现
class Solution {
private static final String[] LETTERS = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
if (digits.length() == 0) {
return res;
}
backtrack(digits, 0, new StringBuilder(), res);
return res;
}
private void backtrack(String digits, int index, StringBuilder path, List<String> res) {
if (index == digits.length()) {
res.add(path.toString());
return;
}
String letters = LETTERS[digits.charAt(index) - '0'];
for (int i = 0; i < letters.length(); i++) {
path.append(letters.charAt(i));
backtrack(digits, index + 1, path, res);
// 撤销当前数字选择的字母,再尝试同一数字的其他候选。
path.deleteCharAt(path.length() - 1);
}
}
}
func letterCombinations(digits string) []string {
if len(digits) == 0 {
return []string{}
}
letters := []string{
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
}
res := make([]string, 0)
path := make([]byte, 0, len(digits))
var backtrack func(int)
backtrack = func(index int) {
if index == len(digits) {
res = append(res, string(path))
return
}
choices := letters[digits[index]-'0']
for i := 0; i < len(choices); i++ {
path = append(path, choices[i])
backtrack(index + 1)
// 撤销当前数字选择的字母,再尝试同一数字的其他候选。
path = path[:len(path)-1]
}
}
backtrack(0)
return res
}
复杂度分析
设非空输入中有
a位对应三个字母、b位对应四个字母,长度为 $d=a+b$,结果数为 $R=3^a4^b$。
- 时间复杂度:$O(Rd)$,搜索生成
R个结果,每个结果需要把长度为d的路径复制成字符串。- 空间复杂度:不计输出为 $O(d)$,路径和递归栈长度均不超过
d;全部结果占 $O(Rd)$。空输入直接返回,耗时和辅助空间为常数。
关键点总结
[!green]
- 一层对应一个数字位置,路径长度与递归下标始终一致。
- 候选由当前位置独立决定,重复数字或重复字母无需排除。
- 撤销只删除当前层加入的最后一个字母,恢复兄弟分支的共同前缀。
- 复杂度必须计入生成所有结果和复制字符串的成本。
易错点总结
[!yellow]
- 空输入直接进入叶子收集,会得到含一个空串的列表,与现有空输入约定不同。
- 返回上层后没有删除末尾字母,后续分支会继承旧选择,路径长度不再对应下标。
- 保存路径后不立即返回,继续读取已越界的数字位置。
- 将
7、9的映射误写成三个字母,会系统性漏掉包含最后一个候选的组合。- 递归调用传入
++index,同时改变本层状态,后续候选会跳过位置;应传index + 1。- 用全局使用标记阻止字母重复,添加了题目没有要求的限制。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 补充题 136. 多组键值选项的笛卡尔积 | 中等 | 每个数字对应一组选项,完整答案就是每组选一个字符的笛卡尔积。 |
| 22. 括号生成 | 中等 | 同样按位置回溯构造字符串,括号题还需要维护前缀合法性,本题各位置选择独立。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!