题目
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

数据范围
1 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。
测试用例
示例1
java
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例2
java
输入:digits = "2"
输出:["a","b","c"]
题解1(博主解法,时间O3m*4k*N,空间On)
java
class Solution {
// 优化点 1: 使用二维字符数组作为查找表 (Mapping)
// 相比 HashMap,数组通过内存偏移量直接寻址,速度更快,且无需处理哈希冲突和对象开销。
// 索引 0 和 1 为空,直接对应电话按键 2-9。
char[][] table = {
{}, {},
{'a', 'b', 'c'}, // 2
{'d', 'e', 'f'}, // 3
{'g', 'h', 'i'}, // 4
{'j', 'k', 'l'}, // 5
{'m', 'n', 'o'}, // 6
{'p', 'q', 'r', 's'}, // 7
{'t', 'u', 'v'}, // 8
{'w', 'x', 'y', 'z'} // 9
};
List<String> res;
int len;
public List<String> letterCombinations(String digits) {
res = new ArrayList<>();
char c[] = digits.toCharArray();
len = c.length;
// 边界情况处理:如果输入为空,直接返回空列表
if (len == 0) {
return res;
}
// 开启 DFS 递归
// c: 输入字符数组
// c[0]-'0': 将字符转换为整数索引 (例如 '2' -> 2),作为初始 pos
// 0: 当前层级 (stage),从第 0 个数字开始
// new StringBuilder(): 用于动态构建字符串,效率优于 String 拼接
dfs(c, c[0] - '0', 0, new StringBuilder());
return res;
}
/**
* 深度优先搜索 (DFS) / 回溯核心方法
* @param c 输入数字的字符数组
* @param pos 当前数字对应的 table 索引 (即当前按键数字)
* @param stage 当前递归深度 (处理到了第几个数字)
* @param curr 当前路径构建的字符串 (状态)
*/
public void dfs(char[] c, int pos, int stage, StringBuilder curr) {
// Base Case (终止条件):
// 当构建的字符串长度等于输入数字的个数时,说明找到如果不一个完整的组合
if (stage == len) {
res.add(curr.toString()); // 将 StringBuilder 转为 String 存入结果集 (耗时 O(N))
return;
}
// 获取当前按键对应的字母集合 (例如 table[2] -> ['a','b','c'])
char ct[] = table[pos];
int tlen = ct.length;
// 遍历当前按键的所有可能字母 (分支)
for (int i = 0; i < tlen; i++) {
// 1. 做选择 (Make Selection)
curr.append(ct[i]);
// 预判下一层的索引 (tpos)
// 逻辑说明:这里你选择在当前层预先计算下一层的 table 索引
int tpos = 0;
if (stage + 1 == len) {
// 如果是最后一层,tpos 的值其实不重要了,因为下一层会直接 return
// 这里赋值为 pos 只是为了防止数组越界报错
tpos = pos;
} else {
// 正常情况:取下一个数字作为下一层的索引
tpos = c[stage + 1] - '0';
}
// 2. 递归进入下一层 (Recurse)
dfs(c, tpos, stage + 1, curr);
// 3. 撤销选择 (Backtrack / Undo Selection)
// 这一步至关重要:在回到上一层之前,必须把刚才加进去的字符删掉,
// 这样才能腾出位置给 for 循环中的下一个字母使用。
curr.delete(stage, stage + 1);
}
}
}
题解2(官解,时空同上)
java
class Solution {
public List<String> letterCombinations(String digits) {
List<String> combinations = new ArrayList<String>();
if (digits.length() == 0) {
return combinations;
}
Map<Character, String> phoneMap = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
backtrack(combinations, phoneMap, digits, 0, new StringBuffer());
return combinations;
}
public void backtrack(List<String> combinations, Map<Character, String> phoneMap, String digits, int index, StringBuffer combination) {
if (index == digits.length()) {
combinations.add(combination.toString());
} else {
char digit = digits.charAt(index);
String letters = phoneMap.get(digit);
int lettersCount = letters.length();
for (int i = 0; i < lettersCount; i++) {
combination.append(letters.charAt(i));
backtrack(combinations, phoneMap, digits, index + 1, combination);
combination.deleteCharAt(index);
}
}
}
}
思路
这道题没有什么好讲思路的,同样就是使用回溯来进行模拟,博客中给了两种写法,分别是官方的和博主的,官方使用map,博主使用静态数组,官方使用StringBuffer,博主使用Stringbuilder,首先静态数组肯定是比map更快的,buffer相较于builder更安全,builder相较于buffer更快。所以用什么方法实现,大家仁者见仁智者见智了。