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