前言:
这是一道回溯算法的典型例题。
题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
解题算法:
Backtracking
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解 排列组合 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
思路:
大致与DFS深度优先遍历一致。功能靠findCombinations方法实现,其中prefix参数是上一次拼接好的字符串,如果它长度符合要求了,说明已经拼好,在结果中添加该字符串并返回即可,如果长度没有达到要求,如传入的数字是23,prefix=“a”,说明还没有拼好,把KEY[a.length]字符串中的三个字母遍历一遍,每次让prefix添加一个字母,然后对其递归调用本方法。比如经过
prefix.append(c);
操作之后,prefix为“ad”,再调用
findCombinations(result, prefix, digits);
传入的prefix长度为2,已经满足要求了,此时本次递归return,倒退到上一层递归的for循环里,此时我们的prefix还是“ad”,如果没有让prefix-1,那么当进入第二次for循环时,prefix.append(c)执行后,prefix就变成了“ade”,这就gg了,所以要递归之后还要让prefix-1,不能使其只增不减。
代码:
public class LetterCombinationsOfAPhoneNumber {
private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) {
if (null == digits || digits.length()==0) return new ArrayList<>();
List<String> result = new ArrayList<>();
findCombinations(result, new StringBuilder(), digits);
return result;
}
private void findCombinations(List<String> result, StringBuilder prefix, String digits) {
if (prefix.length() >= digits.length()) {
result.add(prefix.toString());
return;
}
int curNum = digits.charAt(prefix.length())-'0';
for (char c : KEYS[curNum].toCharArray()) {
prefix.append(c);
findCombinations(result, prefix, digits);
prefix.deleteCharAt(prefix.length()-1);
}
}
}