【题目描述】
【示例】
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
提示:
0 ≤ d i g i t s . l e n g t h ≤ 4 0 \le digits.length \le 4 0≤digits.length≤4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。
【贪心】
【思路】
最开始像个憨批,傻乎乎写了两层for循环,然后发现不能处理输入三个数字的情况,想了半天,才发现不对劲,这题根本没法用循环写出来,然后去看了解答,才发现要用回溯。
贴上官解评论区的一个注释版解答:
// 此题只能用回溯法来做,不能用for,因为输入数字的个数不确定
// 时间复杂度O(3^m + 4^n) m为输入数字中对应三个字符的个数,n为四个
// 空间复杂度O(m + n) 层数最大为数字的长度
// 为什么不能用for循环来做?因为输入数字个数不确定,所以不知道有几层for循环,所以做不了
// 为了遍历所有可能,这题只能用回溯法来做
// 首先可以画出一个树的结构,树根是出发点,第一层表示第一个数字代表的字母,以此类推
// 我们用index表示当前遍历到的层数,初始化index = 0表示现在我们准备考察第一个数字
// 在回溯的过程中,我们需要准备两个东西,一个是ans用来保存最终的答案,还有一个是str
// ans是,当发现回溯出界的时候,即访问到叶子的下一层时,我们将那个时刻的str放入ans
// 而str就是一个动态的字符串,可以理解为是一个stack<char>,层数增加是入栈,返回上一层时出栈
// 如何写好traceback函数呢?首先得确定这个函数得有哪些东西?
// 1. 为了确定每一层都有哪些字母,我们需要输入digits和keyboard
// 2. 为了更新ans和str,我们需要输入这俩哥们儿
// 3. 为了知道现在到哪一层了,我们还得输入index以表示当前层数
// 函数里面的内容应该首先判断边界条件?这很重要,何为边界条件?即啥时候往上跳?
// 我们发现,当我们访问到叶子的下一层时,就该回溯了,此时回溯到的是叶子!
// 此时的index绝对等于digits.size(),因为叶子哪一层是digits.size() - 1
// 如果发现到叶子的下一层了,我们就该更新ans,因为我们发现了一个新的答案,同时往上跳
// 你可能要问了,我能不能在digits.size()这一层的时候就让str弹栈啊?答案是不阔以!
// 你想的很美,觉得此时这一种可能性已经结束了,就想着弹栈。但是你想一下弹栈的时刻仅仅只包括从index到index - 1这一种情况吗?
// 对于叶子结点,由于其没有孩子了,所以在叶子节点处对应的弹栈就是就是从index回来后,我们需要将叶子节点弹出来
// 对于内部结点,我们在什么情况下应该将内部节点弹栈呢?答案是:所有儿子孙子重孙子都被访问过的时候!
// 上面这句话好难理解啊!为此,我们定义:状态 = 结点的位置 + 所有子树是否遍历完毕,结点确定了层数也就确定了,而“所有子树是否遍历完毕”代表是“继续向下”(未进入子树),或者是“刚从子树回到该结点”(子树全部遍历完毕)
// 每种状态是唯一的,并且所有结点(包括内部和叶子)都只会被访问两次,分别对应上面讲的两种状态
// 说白了,我们就是在对所有“状态”进行遍历
// 当“当前状态”为“所有子树遍历完毕”时,就说明以当前状态作为初始状态搜索的所有可能性已经被我们挖掘干净,就要把当前节点给踢出去,即对str进行弹栈
// 回到上面的主题,所以你现在明白为什么不能“只”在index -> index - 1的弹栈了吧?
// 可能又有人要问了,如何用变成语言来描述上面讲的“所有子树遍历完毕”呢?
// 对于每个结点,如果我们发现不是在index层,第一步就是先找出这一层有几种可能性,即keyboard[digits[index]]
// 有了上面的这个string(每个字符代表一种可能性),我们就能往下探索了
// 为什么是往下探索?想一想我们现在的“状态”!是不是属于当前节点的状态1(即未进入子树)?
// 下面就是进入子树,然后对子树进行相同的操作,那traceback的本质就是用来考察第index层的
// 在进入每一个子树之前,我们要先把当前这种可能存进str,然后再进入子树(即调用自身)
// 如果上面的“调用自身”结束了,就说明“那个子树进入了状态2”,就要往上跳,那个子树没用了,就要对str进行弹栈
【代码】
在官解的评论区copy了一份DFS的代码,加了点注释。
class Solution {
public:
string tmp;//可能的字符串
vector<string> res; //答案
vector<string> board={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
void DFS(int pos,string digits){
if(pos==digits.size()){ //遍历到了最后一个数字的位置
res.push_back(tmp);//添加可能的字符串
return;//结束递归
}
int num=digits[pos]-'0';//计算出数字对应的可能字符在board的下标
for(int i=0;i<board[num].size();i++){//对每个可能的字符如:'abc'
tmp.push_back(board[num][i]);//添加到可能的字符串中'a'
DFS(pos+1,digits);//递归,找下一个数字对应的可能字符,'ad'
tmp.pop_back();//删除当前加的那个字符,即把刚刚加上的一个字符('a')删掉,这里是循环'abc',
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0) return res;//输入为空串
DFS(0,digits);//从第一个数字开始找
return res;//返回答案
}
};
这份代码时间100%,空间45%。