Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 60fa8d0

Browse files
Update
1 parent 29f3c9e commit 60fa8d0

11 files changed

+303
-45
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
* [关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)
133133
* [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)
134134
* [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)
135+
* [回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)
135136

136137
(持续更新中....)
137138

@@ -305,6 +306,7 @@
305306
|[0051.N皇后](https://github.com/youngyangyang04/leetcode/blob/master/problems/0051.N皇后.md) |回溯|困难| **回溯**|
306307
|[0052.N皇后II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0052.N皇后II.md) |回溯|困难| **回溯**|
307308
|[0053.最大子序和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |简单|**暴力** **贪心** 动态规划 分治|
309+
|[0055.跳跃游戏](https://github.com/youngyangyang04/leetcode/blob/master/problems/0053.最大子序和.md) |数组 |中等| **贪心** 经典题目|
308310
|[0059.螺旋矩阵II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0059.螺旋矩阵II.md) |数组 |中等|**模拟**|
309311
|[0077.组合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0077.组合.md) |回溯 |中等|**回溯**|
310312
|[0078.子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0078.子集.md) |回溯/数组 |中等|**回溯**|
@@ -328,6 +330,7 @@
328330
|[0113.路径总和II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0113.路径总和II.md) |二叉树树 |简单|**深度优先搜索/递归** **回溯** ****|
329331
|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**|
330332
|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**递归** **迭代/广度优先搜索**|
333+
|[0129.求根到叶子节点数字之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0129.求根到叶子节点数字之和.md) |二叉树 |中等|**递归/回溯** 递归里隐藏着回溯,和113.路径总和II类似|
331334
|[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**|
332335
|[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**|
333336
|[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**|
66 KB
Loading

pics/463.岛屿的周长.png

31.2 KB
Loading

pics/463.岛屿的周长1.png

49.8 KB
Loading

pics/55.跳跃游戏.png

62.4 KB
Loading
Lines changed: 163 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,140 @@
11

2-
32
## 题目地址
43
https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
54

6-
## 思路
5+
> 多个集合求组合问题。
76
8-
本题要解决如下问题:
7+
# 17.电话号码的字母组合
98

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+
# 思路
1324

14-
接下来一一解决这几个问题
25+
从示例上来说,输入"23",最直接的想法就是两层for循环遍历了吧,正好把组合的情况都输出了
1526

27+
如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环.......
28+
29+
大家应该感觉出和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。
30+
31+
理解本题后,要解决如下三个问题:
1632

1733
1. 数字和字母如何映射
34+
2. 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
35+
3. 输入1 * #按键等等异常情况
36+
37+
## 数字和字母如何映射
1838

19-
定义一个二位数组,例如:string letterMap[10],来做映射
39+
可以使用map或者定义一个二位数组,例如:string letterMap[10],来做映射,我这里定义一个二维数组,代码如下:
2040

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+
```
2255

23-
**遇到这种情况,就应该想到回溯了。**
56+
## 回溯法来解决n个for循环的问题
2457

25-
这是一个回溯法的经典题目,**不要以为回溯是一个性能很高的算法,回溯其实就是暴力枚举,纯暴力,搜出所有的可能性。**
58+
对于回溯法还不了解的同学看这篇:[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)
2659

27-
回溯一般都伴随着递归,而这种组合问题,都可以画成一个树形结构。
2860

29-
例如:输入:"23",如图所示:
61+
例如:输入:"23",抽象为树形结构,如图所示:
3062

3163
<img src='../pics/17. 电话号码的字母组合.png' width=600> </img></div>
3264

33-
可以想成遍历这棵树,然后把叶子节点都保存下来,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
65+
图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
3466

67+
回溯三部曲:
3568

36-
3. 输入1 * #按键等等异常情况
69+
* 确定回溯函数参数
3770

38-
题目的测试数据中应该没有异常情况的数据,可以不考虑,但是要知道会有这些异常
71+
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局
3972

73+
再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。
4074

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+
代码如下:
4280

4381
```
44-
backtracking() {
45-
if (终止条件) {
46-
存放结果;
47-
}
82+
vector<string> result;
83+
string s;
84+
void backtracking(const string& digits, int index)
85+
```
4886

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(); // 回溯
54117
}
55118
```
56119

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++代码:
58134

59-
## C++代码
60135

61136
```
137+
// 版本一
62138
class Solution {
63139
private:
64140
const string letterMap[10] = {
@@ -75,18 +151,65 @@ private:
75151
};
76152
public:
77153
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) { // 注意参数的不同
79201
if (index == digits.size()) {
80202
result.push_back(s);
81203
return;
82204
}
83205
int digit = digits[index] - '0';
84206
string letters = letterMap[digit];
85207
for (int i = 0; i < letters.size(); i++) {
86-
getCombinations(digits, index + 1, s + letters[i]);
208+
getCombinations(digits, index + 1, s + letters[i]); // 注意这里的不同
87209
}
88210
}
89211
vector<string> letterCombinations(string digits) {
212+
result.clear();
90213
if (digits.size() == 0) {
91214
return result;
92215
}
@@ -97,8 +220,16 @@ public:
97220
};
98221
```
99222

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+
其实本题不算难,但也处处是细节,大家还要自己亲自动手写一写。
101232

102-
请问为什么 getCombinations(const string& digits, int index, const string& s)函数里的string& s 前要加const,不加的报错
233+
**就酱,如果学到了,就帮Carl转发一波吧,让更多小伙伴知道这里!**
103234

104235
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。

problems/0055.跳跃游戏.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## 链接
2+
https://leetcode-cn.com/problems/jump-game/
3+
4+
## 思路
5+
6+
其实贪心和动态规划很容易混在一起,在面试中,我们应该本着能用贪心就用贪心,贪心解决不了再考虑用动态规划。 毕竟贪心更容易理解,并快速写出代码。
7+
8+
刚看到本题一开始可能想:当前位置元素如果是3,我究竟是跳一步呢,还是两步呢,还是三步呢,究竟跳几步才是最优呢?
9+
10+
其实如果本题是要求只能跳元素数值大小的个数,不能多也不能少,问是否达到终点,那么一定要用动态规划了。
11+
12+
但本题其实我们就看跳到的范围能否覆盖终点,就可以了。
13+
14+
那么我们每次取最大的覆盖范围,看最后能否覆盖终点。
15+
16+
如图:
17+
18+
<img src='../pics/55.跳跃游戏.png' width=600> </img></div>
19+
20+
那么i每次移动只能在cover的范围内移动,每移动一个元素,cover得到该元素数值的补充,让i继续移动下去。
21+
22+
而cover每次只取 得到该元素数值补充后的范围 和 cover本身范围 的最大值。
23+
24+
如果cover大于等于了终点下表,直接return true就可以了。
25+
26+
C++代码如下:
27+
28+
```
29+
class Solution {
30+
public:
31+
bool canJump(vector<int>& nums) {
32+
int cover = 0;
33+
if (nums.size() == 1) return true; // 只有一个元素,就是能达到
34+
for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover
35+
cover = max(i + nums[i], cover);
36+
if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了
37+
}
38+
return false;
39+
}
40+
};
41+
```

problems/0077.组合优化.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ for (int i = startIndex; i <= n; i++) {
7777

7878
2. 还需要的元素个数为: k - path.size();
7979

80-
3. 在集合n中至少要从该起始位置 : n - (k - path.size()) + 1,开始遍历
80+
3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历
8181

8282
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
8383

problems/0129.求根到叶子节点数字之和.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 链接
2+
https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/
13

24
## 思路
35

@@ -122,22 +124,21 @@ private:
122124
}
123125
return sum;
124126
}
125-
// 递归函数不需要返回值,因为我们要遍历整个树
126127
void traversal(TreeNode* cur) {
127128
if (!cur->left && !cur->right) { // 遇到了叶子节点
128129
result += vectorToInt(path);
129130
return;
130131
}
131132
132133
if (cur->left) { // 左 (空节点不遍历)
133-
path.push_back(cur->left->val);
134-
traversal(cur->left); // 递归
135-
path.pop_back(); // 回溯
134+
path.push_back(cur->left->val); // 处理节点
135+
traversal(cur->left); // 递归
136+
path.pop_back(); // 回溯,撤销
136137
}
137138
if (cur->right) { // 右 (空节点不遍历)
138-
path.push_back(cur->right->val);
139-
traversal(cur->right); // 递归
140-
path.pop_back(); // 回溯
139+
path.push_back(cur->right->val); // 处理节点
140+
traversal(cur->right); // 递归
141+
path.pop_back(); // 回溯,撤销
141142
}
142143
return ;
143144
}
@@ -151,3 +152,9 @@ public:
151152
}
152153
};
153154
```
155+
156+
# 总结
157+
158+
过于简洁的代码,很容易让初学者忽视了本题中回溯的精髓,甚至作者本身都没有想清楚自己用了回溯。
159+
160+
**我这里提供的代码把整个回溯过程充分体现出来,希望可以帮助大家看的明明白白!**

0 commit comments

Comments
 (0)