题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
选择算法
这道题很明显是个排列组合问题,那么第一时间想到的就是:
- 递归
- 回溯
这道题递归以及回溯都可以解决。这里讲解回溯的办法好了。
过程
- 选择合适的数据结构存储 数字与字符串的对应关系。 很明显这里需要使用map
定义一个map
//初始化map
Map<Character, String> map = new LinkedHashMap<>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
- 我们在过程中还需要维护一个字符串 tmp,表示已有的字母排列的嘛。
每次进入回溯,就将当前的字符追加到 tmp后面, 当tmp的长度达到了方法输入字符串的长度就可以添加到结果集中去。
伪代码:
List<String> ans = new LinkedList<>();
if(tmp.length() == input.length()) {
ans.add(sb.toString());
return;
}
- 回溯思想:tmp字符串初始为空。每次取电话号码的一位数字,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。然后进行回退操作,遍历其余的字母排列。
代码
class Solution {
//结果集
List<String> ans = new LinkedList<>();
public List<String> letterCombinations(String digits) {
//特殊判断
if(digits.length() == 0)
return ans;
//初始化map
Map<Character, String> map = new LinkedHashMap<>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
char[] chars = digits.toCharArray();
StringBuilder sb = new StringBuilder();
backtrace(0, map, chars, sb);
return ans;
}
public void backtrace(int index, Map<Character, String> map,char[] chars, StringBuilder sb){
//如果sb的容量达到了digits的长度,代表这次回溯已经完成,存入结果集
if(sb.length() == chars.length) {
ans.add(sb.toString());
return;
}
//获取当前数字对应的多个字符
String s = map.get(chars[index]);
for(int i = 0; i < s.length(); i++){
//尝试加入当前数字对应的其中一个字符
sb.append(s.charAt(i));
//回溯
backtrace(index + 1, map, chars, sb);
//删除最后一个加入的字符,达到回溯的目的
sb.deleteCharAt(sb.length() - 1);
}
}
}
思考
其实回溯算法有模板:
void fn(n) {
// 第一步:判断输入或者状态是否非法?
if (input/state is invalid) {
return;
}
// 第二步:判读递归是否应当结束?
if (match condition) {
return some value;
}
// 遍历所有可能出现的情况
for (all possible cases) {
// 第三步: 尝试下一步的可能性
solution.push(case)
// 递归
result = fn(m)
// 第四步:回溯到上一步
solution.pop(case)
}
}
掌握到了这种思路 每次遇到这种题就会觉得很简单了。
更新递归办法:
class Solution {
List<String> ans = new LinkedList<>();
public List<String> letterCombinations(String digits) {
//特殊判断
if(digits.length() == 0)
return ans;
//初始化map
Map<Character, String> map = new LinkedHashMap<>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
char[] chars = digits.toCharArray();
StringBuilder sb = new StringBuilder();
//递归办法 s初始化为 ""
recursion(chars, map, 0, "");
//backtrack(0, map, chars, sb);
return ans;
}
public void recursion(char[] chars, Map<Character, String> map, int index, String s){
//长度达到所求字符串长度之后,即可加入到结果List中
if(s.length() == chars.length){
ans.add(s);
return;
}
//获取当前位置数字所对应的所有字符 (2 - "abc")
String tmp = map.get(chars[index]);
//递归尝试各种符合的字符
for(int i = 0; i < tmp.length(); i++){
//递归 当前数组为2,去对应tmp中的"a",那么 s就更新为 s + "a"
//index + 1 是为了与下一个数组对应的字符串中的字符进行尝试
recursion(chars, map, index + 1, s + tmp.charAt(i));
}
}
}
递归算法效率:
回溯算法效率:
相比较而言,递归方法效率比回溯低下不少。