一 题目
Given a string containing digits from 2-9
inclusive, return all possible letter combinations that the number could represent.
A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.
Example:
Input: "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
二 分析
这个是字符串的题目,medium级别。求数字代表的字母的组合,
先想到的就是暴力循环,时间复杂度是O(4^n) ,取决于输入字符串的长度。怎么遍历就是新问题。
2.1 递归(DFS):
画图很关键有助于厘清思路。递归就是一种深度优先DFS的策略。
画图有两个问题要明白,一个是树的深度,一个是分叉处理。
这里树的深度就是输入字符串的长度(比较直观,每多一位就多一层),
分叉多少,取决于数字是几,也就是初始化是对于数组的值(3个或者4个)。
具体代码实现,对于这个也是分为两部分,一开始是coner case(边界判断),因为LeetCode做几道就知道了,不做校验是过不了的,这个不重要,大不了根据case补充下。
然后是递归的算法,这里要考虑涉及的参数,有字符串的prefix,要处理的digits, 树的高度level,还有输出的结果res.
这里面有两部分,先写基本的返回条件。对于深度优先,就是树的高度达到底部,(生成一条满足标准的结果)。同时加入返回结果。return很重要。
还有就是正常的业务规则,这里很关键,对照上面的图,就是处理 分叉的字符,我们根据数字取出来对应的字符串,遍历这个字符串,然后根据规则,追加到prefix,再把树的高度加1,从图上看,就是取到2对应的字符串“abc”,level 是1,循环先处理a,及对应的level=2,取出3对应的“def”,此时prefix是“a”,level=2.复合条件,把"ad","ae","af"加入并返回,在后面以此类推处理“b”,“c”.
结合代码跟图看会更好理解些。
private final static String[] dict = { "", "", "abc", "def", "ghi",
"jkl", "mno", "pqrs", "tuv", "wxyz" };
//递归
public static List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
if(digits== null || digits.equals("")){
return res;
}else if(digits.length()==1 ){
return Arrays.asList(dict[digits.charAt(0)-'0'].split(""));
}
combinationDFS("", digits, 0, res);
return res;
}
private static void combinationDFS(String prefix, String digits, int level, List<String> res) {
//结束条件
if(level == digits.length() ){
res.add(prefix);
return ;
}
//rule
String s =dict[digits.charAt(level)-'0'];
for(char c:s.toCharArray()){
combinationDFS(prefix+c,digits,level+1,res );
}
}
Runtime: 1 ms, faster than 64.43% of Java online submissions for Letter Combinations of a Phone Number.
Memory Usage: 36.4 MB, less than 98.63% of Java online submissions forLetter Combinations of a Phone Number.
2.2 BFS
上面是深度优先,那么对应的广度优先是啥呢?例如:2,就是a,b,c. 在输入3就是那d,e,f分别追加到a,b,c的后面。先把每个都遍历完,不用递归也可以,使用迭代,我们在遍历 digits 中所有的数字时,我们先建立一个临时的list。还是向上面那样,通过数字到 dict 中取出字符串 str(为了对比测试,这里我使用了map,因为都是O(1)的操作,所以map跟数组没有新能太多的区别),然后遍历取出字符串中的所有字符,再遍历当前结果 res 中的每一个字符串,将字符加到后面,并加入到临时字符串list中。取出的字符串 str 遍历完成后,将临时字符串list赋值给结果 res.
这个就是广度优先的BFS的实现方式,还是深度的更容易理解些。光理解大流程还不行,还得写出来.,没看大神的代码之前,我也卡住了,初始值哪里就没想好。
public static void main(String[] args) {
List<String> res = letterCombinations("234");
System.out.println(JSON.toJSON(res ) );
}
public static List<String> letterCombinations(String digits) {
Map<Character,String> map = new HashMap();
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");
List<String> res = new ArrayList();
if(digits== null || digits.equals("")){
return res;
}else if(digits.length()==1 ){
return Arrays.asList(map.get(digits.charAt(0)).split(""));
}
res.add("");
for(int i=0;i< digits.length() ;i++ ){
List<String> tmp = new ArrayList<>();
for (String t : res) {
String option = map.get(digits.charAt(i));
for(int p=0;p<option.length();p++){
tmp.add(new StringBuilder(t).append(option.charAt(p)).toString() );
}
}
res = tmp;
}
return res;
}
Runtime: 2 ms, faster than 6.88% of Java online submissions for Letter Combinations of a Phone Number.
Memory Usage: 36.4 MB, less than 98.63% of Java online submissions forLetter Combinations of a Phone Number.
时间复杂度一样(4^n),优点就是空间占用小O(n)
总结:
这个题目相对来说考点清晰,就是 关于BFS、DFS,没有掺和其他的算法(基本的api操作还是有的),得理清楚思路,画出递归树那个图来,帮助自己却确定返回条件是什么?业务规则如何去递归。再次感谢花花酱大神的文章。
参考:
https://zxi.mytechroad.com/blog/searching/leetcode-17-letter-combinations-of-a-phone-number/