|
| 1 | + |
| 2 | +<p align="center"> |
| 3 | + <a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a> |
| 4 | + <a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a> |
| 5 | + <a href="https://img-blog.csdnimg.cn/20201210231711160.png"><img src="https://img.shields.io/badge/公众号-代码随想录-brightgreen" alt=""></a> |
| 6 | + <a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a> |
| 7 | +</p> |
| 8 | + |
| 9 | +# 17.电话号码的字母组合 |
| 10 | + |
| 11 | +题目链接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ |
| 12 | + |
| 13 | +给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 |
| 14 | + |
| 15 | +给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 |
| 16 | + |
| 17 | + |
| 18 | + |
| 19 | +示例: |
| 20 | +输入:"23" |
| 21 | +输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. |
| 22 | + |
| 23 | +说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。 |
| 24 | + |
| 25 | +# 思路 |
| 26 | + |
| 27 | +从示例上来说,输入"23",最直接的想法就是两层for循环遍历了吧,正好把组合的情况都输出了。 |
| 28 | + |
| 29 | +如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环....... |
| 30 | + |
| 31 | +大家应该感觉出和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。 |
| 32 | + |
| 33 | +理解本题后,要解决如下三个问题: |
| 34 | + |
| 35 | +1. 数字和字母如何映射 |
| 36 | +2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来 |
| 37 | +3. 输入1 * #按键等等异常情况 |
| 38 | + |
| 39 | +## 数字和字母如何映射 |
| 40 | + |
| 41 | +可以使用map或者定义一个二位数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下: |
| 42 | + |
| 43 | +``` |
| 44 | +const string letterMap[10] = { |
| 45 | + "", // 0 |
| 46 | + "", // 1 |
| 47 | + "abc", // 2 |
| 48 | + "def", // 3 |
| 49 | + "ghi", // 4 |
| 50 | + "jkl", // 5 |
| 51 | + "mno", // 6 |
| 52 | + "pqrs", // 7 |
| 53 | + "tuv", // 8 |
| 54 | + "wxyz", // 9 |
| 55 | +}; |
| 56 | +``` |
| 57 | + |
| 58 | +## 回溯法来解决n个for循环的问题 |
| 59 | + |
| 60 | +对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw) |
| 61 | + |
| 62 | + |
| 63 | +例如:输入:"23",抽象为树形结构,如图所示: |
| 64 | + |
| 65 | + |
| 66 | + |
| 67 | +图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。 |
| 68 | + |
| 69 | +回溯三部曲: |
| 70 | + |
| 71 | +* 确定回溯函数参数 |
| 72 | + |
| 73 | +首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。 |
| 74 | + |
| 75 | +再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。 |
| 76 | + |
| 77 | +注意这个index可不是 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中的startIndex了。 |
| 78 | + |
| 79 | +这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。 |
| 80 | + |
| 81 | +代码如下: |
| 82 | + |
| 83 | +``` |
| 84 | +vector<string> result; |
| 85 | +string s; |
| 86 | +void backtracking(const string& digits, int index) |
| 87 | +``` |
| 88 | + |
| 89 | +* 确定终止条件 |
| 90 | + |
| 91 | +例如输入用例"23",两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。 |
| 92 | + |
| 93 | +那么终止条件就是如果index 等于 输入的数字个数(digits.size)了(本来index就是用来遍历digits的)。 |
| 94 | + |
| 95 | +然后收集结果,结束本层递归。 |
| 96 | + |
| 97 | +代码如下: |
| 98 | + |
| 99 | +``` |
| 100 | +if (index == digits.size()) { |
| 101 | + result.push_back(s); |
| 102 | + return; |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +* 确定单层遍历逻辑 |
| 107 | + |
| 108 | +首先要取index指向的数字,并找到对应的字符集(手机键盘的字符集)。 |
| 109 | + |
| 110 | +然后for循环来处理这个字符集,代码如下: |
| 111 | + |
| 112 | +``` |
| 113 | +int digit = digits[index] - '0'; // 将index指向的数字转为int |
| 114 | +string letters = letterMap[digit]; // 取数字对应的字符集 |
| 115 | +for (int i = 0; i < letters.size(); i++) { |
| 116 | + s.push_back(letters[i]); // 处理 |
| 117 | + backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了 |
| 118 | + s.pop_back(); // 回溯 |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +**注意这里for循环,可不像是在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的**。 |
| 123 | + |
| 124 | +**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!** |
| 125 | + |
| 126 | + |
| 127 | +注意:输入1 * #按键等等异常情况 |
| 128 | + |
| 129 | +代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。 |
| 130 | + |
| 131 | +**但是要知道会有这些异常,如果是现场面试中,一定要考虑到!** |
| 132 | + |
| 133 | + |
| 134 | +## C++代码 |
| 135 | + |
| 136 | +关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的回溯法模板,不难写出如下C++代码: |
| 137 | + |
| 138 | + |
| 139 | +``` |
| 140 | +// 版本一 |
| 141 | +class Solution { |
| 142 | +private: |
| 143 | + const string letterMap[10] = { |
| 144 | + "", // 0 |
| 145 | + "", // 1 |
| 146 | + "abc", // 2 |
| 147 | + "def", // 3 |
| 148 | + "ghi", // 4 |
| 149 | + "jkl", // 5 |
| 150 | + "mno", // 6 |
| 151 | + "pqrs", // 7 |
| 152 | + "tuv", // 8 |
| 153 | + "wxyz", // 9 |
| 154 | + }; |
| 155 | +public: |
| 156 | + vector<string> result; |
| 157 | + string s; |
| 158 | + void backtracking(const string& digits, int index) { |
| 159 | + if (index == digits.size()) { |
| 160 | + result.push_back(s); |
| 161 | + return; |
| 162 | + } |
| 163 | + int digit = digits[index] - '0'; // 将index指向的数字转为int |
| 164 | + string letters = letterMap[digit]; // 取数字对应的字符集 |
| 165 | + for (int i = 0; i < letters.size(); i++) { |
| 166 | + s.push_back(letters[i]); // 处理 |
| 167 | + backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了 |
| 168 | + s.pop_back(); // 回溯 |
| 169 | + } |
| 170 | + } |
| 171 | + vector<string> letterCombinations(string digits) { |
| 172 | + s.clear(); |
| 173 | + result.clear(); |
| 174 | + if (digits.size() == 0) { |
| 175 | + return result; |
| 176 | + } |
| 177 | + backtracking(digits, 0); |
| 178 | + return result; |
| 179 | + } |
| 180 | +}; |
| 181 | +``` |
| 182 | + |
| 183 | +一些写法,是把回溯的过程放在递归函数里了,例如如下代码,我可以写成这样:(注意注释中不一样的地方) |
| 184 | + |
| 185 | +``` |
| 186 | +// 版本二 |
| 187 | +class Solution { |
| 188 | +private: |
| 189 | + const string letterMap[10] = { |
| 190 | + "", // 0 |
| 191 | + "", // 1 |
| 192 | + "abc", // 2 |
| 193 | + "def", // 3 |
| 194 | + "ghi", // 4 |
| 195 | + "jkl", // 5 |
| 196 | + "mno", // 6 |
| 197 | + "pqrs", // 7 |
| 198 | + "tuv", // 8 |
| 199 | + "wxyz", // 9 |
| 200 | + }; |
| 201 | +public: |
| 202 | + vector<string> result; |
| 203 | + void getCombinations(const string& digits, int index, const string& s) { // 注意参数的不同 |
| 204 | + if (index == digits.size()) { |
| 205 | + result.push_back(s); |
| 206 | + return; |
| 207 | + } |
| 208 | + int digit = digits[index] - '0'; |
| 209 | + string letters = letterMap[digit]; |
| 210 | + for (int i = 0; i < letters.size(); i++) { |
| 211 | + getCombinations(digits, index + 1, s + letters[i]); // 注意这里的不同 |
| 212 | + } |
| 213 | + } |
| 214 | + vector<string> letterCombinations(string digits) { |
| 215 | + result.clear(); |
| 216 | + if (digits.size() == 0) { |
| 217 | + return result; |
| 218 | + } |
| 219 | + getCombinations(digits, 0, ""); |
| 220 | + return result; |
| 221 | +
|
| 222 | + } |
| 223 | +}; |
| 224 | +``` |
| 225 | + |
| 226 | +我不建议把回溯藏在递归的参数里这种写法,很不直观,我在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)这篇文章中也深度分析了,回溯隐藏在了哪里。 |
| 227 | + |
| 228 | +所以大家可以按照版本一来写就可以了。 |
| 229 | + |
| 230 | +# 总结 |
| 231 | + |
| 232 | +本篇将题目的三个要点一一列出,并重点强调了和前面讲解过的[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[216.组合总和III](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)的区别,本题是多个集合求组合,所以在回溯的搜索过程中,都有一些细节需要注意的。 |
| 233 | + |
| 234 | +其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。 |
| 235 | + |
| 236 | +------------------------ |
| 237 | + |
| 238 | +* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) |
| 239 | +* B站:[代码随想录](https://space.bilibili.com/525438321) |
| 240 | +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) |
| 241 | + |
| 242 | + |
| 243 | + |
0 commit comments