读题心路历程:这道题读完的第一感觉是,感觉很简单,就建立一个map映射,然后就for循环就完事了,但是真按照这个思路写起来,就越写越蒙了,越写越发现,不知道该写几层for循环了,像是“23”就写两层for循环就能找出9个字符串了,但是如果“234”那就是得三层for循环了,如果“2345”那就是四层for循环了,所以这个for循环的层数控制不了,当然如果在这道题中,digtis长度从0-4,完全可以分情况,看这个digits有几个,就if几部分,然后把情况都列出来。但是真正解题不能这么来解,如果digits有n个呢?
所以在这里就选择利用回溯法——可以解决不知道for几层的场景。这个回溯的概念到底是什么呢?简单来说,就是利用递归,在树形结构上进行搜索,当搜索完一条路之后,就回到上一步,再搜一遍,直到彻底搜完为止,这样就可以解决我们手动for循环的问题。
回到这道题:这道题也可以抽象为一个树结构:
遍历digits=“23”,遍历到2的时候,找到对应的字符串“abc”,然后此时遍历a,再去遍历到3对应的“def”,然后让a和“def”进行组合,同理,大致的流程如图所示。
具体代码:
class Solution {
public:
vector<string> result; //定义全局变量,用来存放最后的答案。
string s; //定义一个字符串,用来临时存放,遍历过程中的答案。
map<char, string> list =
{
{'2',"abc"},
{'3',"def"},
{'4',"ghi"},
{'5',"jkl"},
{'6',"mno"},
{'7',"pqrs"},
{'8',"tuv"},
{'9',"wxyz"}
}; //建立一个Map映射,存放数字和字符串对应关系,这里也可以用数组。
//首先,想用回溯方法,就需要确定回溯方法的参数,在这里,回溯函数backtracking的参数为digtis和index,index是序列号,也就是用来遍历digtis的。
void backtracking(const string& digits, int index) {
//当确定好,回溯函数参数的之后,还需要确定函数递归的结束条件。那什么时候本次递归结束呢,从树结构的角度来看,肯定是,遍历到树的叶子节点后,把答案push_back到result里后,就可以结束本次递归了。也就下边这个if。当index==digits的长度后,就代表现在已经遍历完digits了,就该结束了。
if (index == digits.size())
{
result.push_back(s);
return;
}
string letters = list[digits[index]]; //定义一个string用来存放“23”中的2或者3对应的字符串。
for (int i = 0; i < letters.size(); i++) //当第一次遍历“2”的时候,需要遍历“2”对应的“abc”。
{
s.push_back(letters[i]);//把“a”放进字符串s中。
//然后该再次调用回溯函数进行递归了,需要改变的数字也就是这个index,当index+1后,就代表此时遍历的数字从2到了3,意思就是当我取到2中“abc”中的“a“之后,我就需要找到3对应的“def"了,然后再分别和'd''e''f'进行组合。
backtracking(digits, index + 1);
s.pop_back();//这个pop_back就是回溯方法中最重要的回溯的一步了。
}
}
public:
vector<string> letterCombinations(string digits) {
s.clear();
result.clear();
if (digits.size() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}
};