简介
题目链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/
解决方式:字符串 + 回溯法(DFS、递归)/ 队列迭代法(BFS、队列)
回溯法
实现思路:
回溯法也就是递归,思路是很直接的思路,即得到数字对于的字符串后枚举每个字符的排列组合,只不过枚举的具体过程采用了深度优先搜索算法。
java
class Solution {
// 结果集合
private List<String> answer = new ArrayList<>();
// 哈希映射
private Map<Character, String> map = new HashMap<>(){
{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}
};
public List<String> letterCombinations(String digits) {
// 边界处理
if(digits == "" || digits.length() == 0) return answer;
// 回溯法(递归)
backtrack(digits, 0, new StringBuilder());
// 返回结果
return answer;
}
private void backtrack(String digits, int index, StringBuilder path){
// 递归终止条件
if(index == digits.length()){
answer.add(path.toString());
return;
}
// 递归
// DFS排列组合字母
char digit = digits.charAt(index);
String letter = map.get(digit);
for(int i = 0; i < letter.length(); i++){
path.append(letter.charAt(i));
backtrack(digits, index + 1, path);
path.deleteCharAt(path.length() - 1);
}
}
}
算法执行流程:
1. 初始化
res为空列表,用于存放最终结果。map定义了按键'2'→"abc",'3'→"def"。- 调用
letterCombinations("23"),digits不为空,进入backtrack(digits, 0, new StringBuilder())。
2. 第一次进入 backtrack(index = 0)
index = 0,digits.length() = 2,不相等,继续。- 获取
digit = digits.charAt(0) = '2'。 letters = map.get('2') = "abc"。- 遍历
"abc"的每个字符:
第一层循环(i = 0,字母 'a')
path.append('a')→path变为"a"。- 递归调用
backtrack(digits, 1, path)。
3. 第二次进入 backtrack(index = 1)
index = 1,不等于长度 2。digit = digits.charAt(1) = '3'。letters = "def"。- 遍历
"def"的每个字符:
第二层循环(i = 0,字母 'd')
path.append('d')→path变为"ad"。- 递归调用
backtrack(digits, 2, path)。
第三次进入 backtrack(index = 2)
index = 2,等于digits.length(),将path.toString()="ad"加入res。- 返回到上一层(index = 1,刚执行完
backtrack(digits, 2, path)的那一行)。
回溯(在 index = 1 的循环内)
- 执行
path.deleteCharAt(path.length() - 1),删除最后一个字符'd',path恢复为"a"。
继续第二层循环(i = 1,字母 'e')
path.append('e')→"ae"。- 递归调用
backtrack(digits, 2, path)。 - 此时
index = 2,加入"ae"到res。 - 回溯删除
'e',path恢复"a"。
继续第二层循环(i = 2,字母 'f')
path.append('f')→"af"。- 递归调用,加入
"af"到res。 - 回溯删除
'f',path恢复"a"。
第二层循环结束,返回到第一层(index = 0,i = 0 的那个递归调用之后)。
4. 回溯到第一层循环(i = 0)
- 执行
path.deleteCharAt(path.length() - 1),删除'a',path变为空。
第一层循环(i = 1,字母 'b')
path.append('b')→"b"。- 递归调用
backtrack(digits, 1, path)。
进入 index = 1 的递归:
- 遍历
"def",依次得到"bd"、"be"、"bf",加入res。 - 回溯删除
'b',path空。
第一层循环(i = 2,字母 'c')
- 同理,生成
"cd"、"ce"、"cf",加入res。
5. 最终结果
res 中包含 ["ad","ae","af","bd","be","bf","cd","ce","cf"],按任意顺序返回。
核心思想总结
- 深度优先搜索(DFS):从第一个数字开始,依次为每个数字选择一个字母,组合成字符串。
- 回溯:每完成一个完整组合(到达叶子节点)后,撤销最后一步的选择,尝试下一个分支。
- 路径维护 :使用
StringBuilder动态构建当前组合,避免频繁创建新字符串。
这就是回溯法解决该题目的完整流程。
队列迭代法
实现思路:
队列迭代法采取的是广度优先搜索算法,即层次构建的思路。通过队列的循环入队出队操作,排列组合每一个字符组合。
java
class Solution {
public List<String> letterCombinations(String digits) {
// 结果集合
List<String> ans = new ArrayList<>();
// 边界处理
if(digits == "" || digits.length() == 0) return ans;
// 初始化队列和字符串数组
String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
Queue<String> queue = new LinkedList<>();
// 添加空字符串,方便后续添加字符
queue.offer("");
// BFS遍历字符
for(int i = 0; i < digits.length(); i++){
int index = digits.charAt(i) - '0';
String letter = map[index];
// 迭代队列
int size = queue.size();
for(int j = 0; j < size; j++){
String prefix = queue.poll();
for(char c : letter.toCharArray()){
queue.offer(prefix + c);
}
}
}
// 转换
ans.addAll(queue);
// 返回结果
return ans;
}
}
算法执行流程:
初始状态
队列 queue = [""](一个空字符串,作为起始前缀)。
处理第一个数字 '2'
letters = "abc"- 当前队列大小
size = 1 - 弹出
prefix = ""- 遍历
'a':"" + 'a' = "a"入队 → 队列变为["a"] - 遍历
'b':"b"入队 → 队列变为["a", "b"] - 遍历
'c':"c"入队 → 队列变为["a", "b", "c"]
- 遍历
- 第一轮循环结束,队列中为
["a", "b", "c"]
处理第二个数字 '3'
letters = "def"- 当前队列大小
size = 3(注意:这个大小是在本轮循环开始前记录的,即上一轮结束时的队列长度) - 依次弹出三个元素:
弹出 "a"
- 拼接
'd'→"ad"入队 → 队列:["b", "c", "ad"] - 拼接
'e'→"ae"入队 → 队列:["b", "c", "ad", "ae"] - 拼接
'f'→"af"入队 → 队列:["b", "c", "ad", "ae", "af"]
弹出 "b"
- 拼接
'd'→"bd"入队 → 队列:["c", "ad", "ae", "af", "bd"] - 拼接
'e'→"be"入队 → 队列:["c", "ad", "ae", "af", "bd", "be"] - 拼接
'f'→"bf"入队 → 队列:["c", "ad", "ae", "af", "bd", "be", "bf"]
弹出 "c"
- 拼接
'd'→"cd"入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd"] - 拼接
'e'→"ce"入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce"] - 拼接
'f'→"cf"入队 → 队列:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
处理结束
- 队列中的内容为:
["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] - 这就是所有字母组合。
核心思想总结
- 广度优先搜索(BFS):每一轮循环处理一个数字,将当前所有部分组合(队列中的字符串)扩展为更长的组合。
- 逐层构建 :初始队列只有空串,处理完第 1 个数字后队列长度为 3(
"a","b","c"),处理完第 2 个数字后队列长度变为 9,即所有 3×3 的组合。 - 空间换时间:队列中会同时存储所有中间状态的组合,但最终结果也来自同一个队列。