Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit60fa8d0

Browse files
Update
1 parent29f3c9e commit60fa8d0

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
<imgsrc='../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+
<imgsrc='../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)

[8]ページ先頭

©2009-2025 Movatter.jp