问题链接:https://oj.leetcode.com/problems/letter-combinations-of-a-phone-number/
问题描述:Given a digit string, return all possible letter combinations that the number could represent.
关于数字和字母的mapping请参考链接内的图
API:public List<String> letterCombinations(String digits)
问题分析:本题目其实并不是字符串的处理题,相反其实是一道比较基础的图论题。在目前的面试阶段里,深度搜索dfs和广度搜索bfs的灵活运用应该能处于扫遍大部分的情况。本题也是这样的。接下来将就这两种算法给出不同的答案。
算法步骤如下:
1.根据数字和字符的mapping,我们可以先建立对应的哈希表作为缓存调用,这样代码写起来比较方便。
2.然后进行dfs或者bfs的图构建。实际上这就是一个建图的过程,到最后结果就是图分支里最底层的所有节点。
先给出的是dfs算法代码:
public List<String> letterCombinations(String digits) {
HashMap<Character, char[]> cached = new HashMap<Character, char[]>();
for(int i = 0; i <= 9; i++){
Character c = (char)('0' + i);
switch(c){
case '0':
cached.put(c, "0".toCharArray());
break;
case '1':
cached.put(c, "1".toCharArray());
break;
case '2':
cached.put(c, "abc".toCharArray());
break;
case '3':
cached.put(c, "def".toCharArray());
break;
case '4':
cached.put(c, "ghi".toCharArray());
break;
case '5':
cached.put(c, "jkl".toCharArray());
break;
case '6':
cached.put(c, "mno".toCharArray());
break;
case '7':
cached.put(c, "pqrs".toCharArray());
break;
case '8':
cached.put(c, "tuv".toCharArray());
break;
case '9':
cached.put(c, "wxyz".toCharArray());
break;
default:
break;
}
}
List<String> res = new LinkedList<String>();
dfshelper(res, 0, digits, cached, new StringBuilder());
return res;
}
public void dfshelper(List<String> res, int curlevel, String digits, HashMap<Character, char[]> cached, StringBuilder buf){
if(curlevel == digits.length()){
res.add(buf.toString());
}else{
char[] num_to_char = cached.get(digits.charAt(curlevel));
for(char c : num_to_char){
StringBuilder next_buf = new StringBuilder(buf);
next_buf.append(c);
dfshelper(res, curlevel + 1, digits, cached, next_buf);
}
}
}
dfs作为深度搜索一般都以递归为最先考虑到的实现方案。当然,也可以用Stack来储存当前状态来进行循环做,以后的pre-order和in-order还有post-order遍历二叉树的解里都可以看的到。但是一旦需要储存的状态比较复杂,这个stack就比较让人痛苦了。并且这种做法本质上只是代替了编译器做状态储存和提取的递归,在没有很苛刻的performance要求的情况下,并没有必要。
接下来给出的便是bfs的解题代码:
public List<String> letterCombinations(String digits) {
HashMap<Character, char[]> cached = new HashMap<Character, char[]>();
for(int i = 0; i <= 9; i++){
Character c = (char)('0' + i);
switch(c){
case '0':
cached.put(c, "0".toCharArray());
break;
case '1':
cached.put(c, "1".toCharArray());
break;
case '2':
cached.put(c, "abc".toCharArray());
break;
case '3':
cached.put(c, "def".toCharArray());
break;
case '4':
cached.put(c, "ghi".toCharArray());
break;
case '5':
cached.put(c, "jkl".toCharArray());
break;
case '6':
cached.put(c, "mno".toCharArray());
break;
case '7':
cached.put(c, "pqrs".toCharArray());
break;
case '8':
cached.put(c, "tuv".toCharArray());
break;
case '9':
cached.put(c, "wxyz".toCharArray());
break;
default:
break;
}
}
List<String> res = new LinkedList<String>();
int cur_size = 1;
int cur_level = 0;
Queue<StringBuilder> bfs_queue = new LinkedList<StringBuilder>();
bfs_queue.offer(new StringBuilder());
while(cur_level != digits.length()){
StringBuilder cur_buf = bfs_queue.remove();
char[] num_to_char = cached.get(digits.charAt(cur_level));
for(char c : num_to_char){
StringBuilder next_buf = new StringBuilder(cur_buf);
next_buf.append(c);
bfs_queue.offer(next_buf);
}
cur_size--;
if(cur_size == 0){
cur_size = bfs_queue.size();
cur_level++;
}
}
while(!bfs_queue.isEmpty())
res.add(bfs_queue.poll().toString());
return res;
}
对比之下,bfs一般都是以queue作为载体来进行循环的一个过程。一般都需要用到类似cur_size和cur_level的变量来记录每一层的节点数目和当前的层数。
dfs和bfs相比,dfs的特点在与探索分支的状态,也就是可以先返回更深层次的状态。bfs的特点在于是一层层往下检索,而且比较容易用循环实现,在寻找图中某个特别的节点的时候,bfs效率一般更高,因为循环比较容易终结,相反dfs就要采取比较非常规的方法了,譬如全局变量之类的,又或者c++里面可以放入reference来做近似的全局处理,java就办不到了。