力扣刷题笔记-电话号码的字母组合
力扣题目-电话号码的字母组合C++实现
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
分析:
这个问题问题本质是“组合枚举”,每个数字可以对应多个字母,每个字母都可能与后面的字母组合形成完整结果。我们要列出所有可能组合,也就是遍历每一种可能的选择。而回溯法恰好就是这样,沿着一条选择路径一直走到底,完成后回退到上一个选择点尝试其他可能,直到所有路径都遍历完。就像走迷宫,刚开始先选择a这个口,沿着 ‘a’ → ‘d’、‘e’、‘f’ 一条条走到底生成 “ad”,“ae”,“af”,然后回到之前的分叉口尝试 ‘b’ → ‘d’,‘e’,‘f’,生成 “bd”,“be”,“bf”,再回去尝试 ‘c’ → “cd”,“ce”,“cf”,直到所有路径走完。
定义一个数字到字母的映射表 mapping,然后用一个字符串 path 来保存当前正在构建的组合,表示回溯过程中不断添加和撤销选择的临时路径。mapping中的下标恰好和数字完全对应,比如mapping[2] 代表 “abc”(数字2对应的字母a、b、c),这样可以快速找到数字对应的字母,在回溯中遍历生成组合。
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
字母 | 无 | 无 | abc | def | ghi | jkl | mno | pqrs | tuv | wxyz |
把输入的数字字符串比如23存在input中,然后先获取到里面的内容’2’和’3’,注意这里获取到的2和3是字符类型的,如果想利用mapping表,必须把这个字符转成整数类型,这样2和3就可以作为mapping的下标找到mapping[2]是’abc’,mapping[3]是’def’。
然后把2对应的abc和3对应的def进行分层处理。
这里会进行递归处理,index 代表当前正在处理的数字在输入字符串中的索引,每递归一次,index 加 1(index + 1),表示 “处理完当前数字,该处理下一个数字了”。
当 index 等于输入字符串的长度(input.size())时,表示所有数字都已处理完毕。当前构建的组合(path)是一个完整有效的结果,其实这里的index的值也就等于当前构建组合path的长度。每处理完一个数字,就会在 path 中添加一个字母(长度加 1),index的值这时也加1了。这也是为什么终止条件可以用 index == input.size() 来判断 —— 因为此时 path 的长度必然也等于输入长度,已经形成了完整组合。
index初始值为0,先处理第一个数字2。要循环遍历 2’对应的 a/b/c,首先先把数字2对应的a加入到path中,path 本身就像一个栈,用来临时存储 “当前路径”,把a加入路径之后,进入下一层递归(index=1),循环遍历 ‘3’ 对应的 d/e/f,遍历的时候想要再进行下一层递归,但是由于这个时候index=2,已经到了叶子结点了,这时候path里也有两位字母了,可以进行返回。这时第二层3对应的叶子节点如d已经遍历到了,path里是ad,此时要进行回溯,回溯到第一层,相当于把path中的d进行了出栈,现在还剩下第一层的a。回到第 1 层节点a之后,会再去尝试第二层的下一个字母(如 e),然后处理完又会回溯到a去走向下一层的f,直到def全部都遍历完。
class Solution {
public:// 生成数字字符串对应的所有字母组合 vector<string> letterCombinations(string input) {vector<string> res;if (input.empty()) return res;// 数字到字母的映射表(索引对应0-9) vector<string> mapping = {"", "", "abc", "def", "ghi","jkl", "mno", "pqrs", "tuv", "wxyz"};string path; // 存储当前构建的组合 backtrack(input, 0, path, res, mapping);return res;}
private:// 回溯函数:递归生成所有可能组合 // input:输入的数字串;index:当前处理位置;path:当前组合;res:结果集;mapping:映射表 void backtrack(const string& input, int index, string& path, vector<string>& res, const vector<string>& mapping) {// 终止条件:组合长度等于输入长度时,加入结果集 if (index == input.size()) {res.push_back(path);return;}// 1. 获取当前数字字符 char currentChar = input[index];//当前数字字符并转为整数(如'2'->2),这样可以获取mapping的下标 int digit = currentChar - '0'; // 2. 从映射表获取对应字母(如2->"abc") string letters = mapping[digit];// 尝试每个字母,递归构建组合后回溯 for (char c : letters) {path.push_back(c); // 选择字母 backtrack(input, index + 1, path, res, mapping); // 处理下一个数字 path.pop_back(); // 回溯,移除当前字母}}
};
用例子输入23来走一遍代码的流程:
调用 backtrack(“23”, index=0, path=“”, res, mapping)
1.处理第 1 个数字 ‘2’(index=0,letters=“abc”)
1.1 选字母 ‘a’
操作:path.push_back(‘a’) → path=“a”
递归调用 backtrack(“23”, index=1, path=“a”, res, mapping),index变为1准备处理数字3对应的字母
2.处理第 2 个数字 ‘3’(index=1,path=“a”)
2.1 选 ‘d’
操作:path.push_back(‘d’) → path=“ad”
递归调用 backtrack(“23”, index=2, path=“ad”, res, mapping)
index=2 = input.size() → 终止条件 → “ad” 加入 res
返回上一层(index=1,path=“ad”)
回溯:path.pop_back() → path=“a”
2.2 选 ‘e’
操作:path.push_back(‘e’) → path=“ae”
递归调用 index=2 = input.size() → 终止条件→ “ae” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“a”
2.3 选 ‘f’
操作:path.push_back(‘f’) → path=“af”
递归调用 index=2 = input.size() → 终止条件→ “af” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“a”
第 2 层循环结束 → 返回第 1 个数字层(index=0)
回溯:path.pop_back() → path=“”
3.第 1 个数字 '2’继续循环
3.1 选 ‘b’
操作:path.push_back(‘b’) → path=“b”
递归调用 backtrack(“23”, index=1, path=“b”, res, mapping),index变为1准备处理数字3对应的字母
4.处理第 2 个数字 ‘3’(index=1,path=“b”)
4.1 选 ‘d’
操作:path.push_back(‘d’) → path=“bd”
递归调用 index=2 = input.size() → 终止条件→ “bd” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“b”
4.2 选 ‘e’
操作:path.push_back(‘e’) → path=“be”
递归调用 index=2 = input.size() → 终止条件→ “be” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“b”
4.3 选 ‘f’
操作:path.push_back(‘f’) → path=“bf”
递归调用 index=2 = input.size() → 终止条件→ “bf” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“b”
第 2 层循环结束 → 返回第 1 个数字层(index=0)
回溯:path.pop_back() → path=“”
5.第 1 个数字 '2’继续循环
5.1 选 ‘c’
操作:path.push_back(‘c’) → path=“c”
递归调用 backtrack(“23”, index=1, path=“c”, res, mapping),index变为1准备处理数字3对应的字母
6.第 2 个数字 ‘3’(index=1,path=“c”)
6.1 选 ‘d’
操作:path.push_back(‘d’) → path=“cd”
递归调用 backtrack(“23”, index=2, path=“cd”, res, mapping)
index=2 = input.size() → 终止条件 → “cd” 加入 res
返回上一层(index=1,path=“cd”)
回溯:path.pop_back() → path=“c”
6.2 选 ‘e’
操作:path.push_back(‘e’) → path=“ce”
递归调用 index=2 = input.size() → 终止条件→ “ce” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“c”
6.3 选 ‘f’
操作:path.push_back(‘f’) → path=“cf”
递归调用 index=2 = input.size() → 终止条件→ “cf” 加入 res
返回上一层 → 回溯:path.pop_back() → path=“c”
第 2 层循环结束 → 返回第 1 个数字层(index=0)
回溯:path.pop_back() → path=“”
最终结果:
res = [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]