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

Commit 7343c01

Browse files
Update
1 parent 4c2a526 commit 7343c01

11 files changed

+371
-46
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@
123123
* [二叉树:搜索树中的删除操作](https://mp.weixin.qq.com/s/-p-Txvch1FFk3ygKLjPAKw)
124124
* [二叉树:修剪一棵搜索树](https://mp.weixin.qq.com/s/QzmGfYUMUWGkbRj7-ozHoQ)
125125
* [二叉树:构造一棵搜索树](https://mp.weixin.qq.com/s/sy3ygnouaZVJs8lhFgl9mw)
126-
126+
* [二叉树:搜索树转成累加树](https://mp.weixin.qq.com/s/hZtJh4T5lIGBarY-lZJf6Q)
127+
* [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](https://mp.weixin.qq.com/s/-ZJn3jJVdF683ap90yIj4Q)
127128

128129

129130
(持续更新中....)

pics/77.组合1.png

122 KB
Loading

pics/77.组合2.png

110 KB
Loading

pics/77.组合3.png

109 KB
Loading

problems/0053.最大子序和.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
> 笔者在BAT从事技术研发多年,利用工作之余重刷leetcode,更多原创技术文章欢迎关注「代码随想录」,校招社招求职内推欢迎通过公众号「代码随想录」联系我,度厂很缺人!
2-
3-
> 笔者在BAT从事技术研发多年,利用工作之余重刷leetcode,希望结合自己多年的实践经验,把算法讲的更清楚,更多原创文章欢迎关注公众号「代码随想录」。
41

52
## 题目地址
63
https://leetcode-cn.com/problems/maximum-subarray/

problems/0077.组合.md

Lines changed: 95 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
12
# 第77题. 组合
3+
4+
题目链接:https://leetcode-cn.com/problems/combinations/
5+
26
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
37
示例:
48

@@ -13,6 +17,7 @@
1317
[1,4],
1418
]
1519

20+
1621
# 思路
1722

1823
这是回溯法的经典题目。
@@ -33,65 +38,122 @@
3338
那么就三层for循环,代码如下:
3439

3540
```
36-
for (int i = 1; i <= n; i++) {
37-
for (int j = i + 1; j <= n; j++) {
38-
for (int u = j + 1; u <=n; n++) {
41+
for (int i = 1; i <= n; i++) {
42+
for (int j = i + 1; j <= n; j++) {
43+
for (int u = j + 1; u <=n; n++) {
3944
40-
}
4145
}
4246
}
47+
}
4348
```
44-
**如果n为 100,k为50呢,那就50层for循环,是不是开始窒息。**
49+
50+
**如果n为100,k为50呢,那就50层for循环,是不是开始窒息**
4551

4652
那么回溯法就能解决这个问题了。
4753

48-
回溯是用来做选择的,递归用来节点层叠嵌套**每一次的递归是层叠嵌套的关系,可以用于解决多层嵌套循环的问题。**
54+
回溯是用来做选择,递归用来做节点层叠嵌套(可以理解是随便开K的for循环)**每一次的递归相当于嵌套一个for循环,可以用于解决多层嵌套循环的问题了**
4955

50-
其实子集和组合问题都可以抽象为一个树形结构,如下:
56+
**回溯问题都可以抽象为一棵树形结构!用树形结构来理解回溯就容易多了**
5157

58+
那么我们把组合问题抽象为如下树形结构:
5259

5360
<img src='../pics/77.组合.png' width=600> </img></div>
5461

55-
可以看一下这个棵树,一开始集合是 1,2,3,4, 从左向右去数,取过的数,不在重复取。
62+
可以看出这个棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不在重复取。
63+
64+
第一次取1,集合变为2,3,4 ,因为k为2,我们只需要再取一个数就可以了,分别取,2,3,4, 得到集合[1,2] [1,3] [1,4],以此类推。
65+
66+
**回溯的问题都可以抽象为一个树形结构,在求解组合问题的过程中,n相当于树的宽度,k相当于树的深度**
67+
68+
**每次从集合中选组元素,可选择的范围随着选择的进行而限缩,调整可选择的范围**
69+
70+
如何在这个树上遍历,然后收集到我们要的结果集呢?
5671

57-
第一取1,集合变为2,3,4 ,因为k为2,我们只需要去一个数就可以了,分别取,2,3,4, 得到集合[1,2] [1,3] [1,4],以此类推
72+
用的就是回溯搜索法,**可以发现,每次搜索到了叶子节点,我们就找到了一个结果**
5873

59-
**其实这就转化成从集合中选取子集的问题,可选择的范围随着选择的进行而限缩,于是做剪枝,调整可选择的范围**
6074

61-
如何在这个树上遍历,然后收集到我们要的结果集呢,用的就是回溯搜索法,**可以发现,每次搜索到了叶子节点,我们就找到了一个结果。**
75+
**这份模板,大家可以要记住了,后面做回溯搜索的题目,都离不开这个模板**
76+
77+
## 求组合
78+
79+
掌握了模板之后,我们再来看一下这道求组合的题目。
80+
81+
* 回溯函数返回值以及参数
82+
83+
在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
84+
85+
代码如下:
6286

63-
分析完过程,我们来看一下 回溯算法的模板框架如下:
6487
```
65-
backtracking() {
66-
if (终止条件) {
67-
存放结果;
68-
}
88+
vector<vector<int>> result; // 存放符合条件结果的集合
89+
vector<int> path; // 用来存放符合条件结果
90+
```
6991

70-
for (选择:选择列表(可以想成树中节点孩子的数量)) {
71-
递归,处理节点;
72-
backtracking();
73-
回溯,撤销处理结果
74-
}
92+
其实不定义这两个全局遍历也是可以的,把这两个变量放进回溯函数的参数里,但为了函数里参数太多影响可读性,所以我定义全局变量。
93+
94+
首先两个参数,集合n里面取k的数,是两个int型的变量。
95+
96+
然后还需要一个参数,也为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
97+
98+
为什么要有这个startIndex呢?
99+
100+
从下图中红线部分可以看出,在集合[1,2,3,4]取1之后,下一层递归,就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢,靠的就是startIndex。
101+
102+
<img src='../pics/77.组合2.png' width=600> </img></div>
103+
104+
所以需要startIndex来记录下一层递归,搜索的起始位置。
105+
106+
那么整体代码如下:
107+
108+
```
109+
vector<vector<int>> result; // 存放符合条件结果的集合
110+
vector<int> path; // 用来存放符合条件单一结果
111+
void backtracking(int n, int k, int startIndex)
112+
```
113+
114+
* 回溯函数终止条件
115+
116+
什么时候到达所谓的叶子节点了呢?
117+
118+
就是path这个数组的大小如果达到k,说明我们找到了一个集合大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。
119+
120+
如图红色部分:
121+
122+
<img src='../pics/77.组合3.png' width=600> </img></div>
123+
124+
此时用result二维数组,把path保存起来,并终止本层递归。
125+
126+
所以终止条件代码如下:
127+
128+
```
129+
if (path.size() == k) {
130+
result.push_back(path);
131+
return;
75132
}
76133
```
77134

78-
分析模板:
79135

80-
什么是达到了终止条件,树中就可以看出,搜到了叶子节点了,就找到了一个符合题目要求的答案,就把这个答案存放起来。
136+
* 回溯搜索的遍历过程
81137

82-
看一下这个for循环,这个for循环是做什么的,for 就是处理树中节点各个孩子的情况, 一个节点有多少个孩子,这个for循环就执行多少次。
138+
在如下如中,我们知道for循环用来横向遍历,递归的过程是纵向遍历。
139+
<img src='../pics/77.组合1.png' width=600> </img></div>
83140

84-
最后就要看这个递归的过程了,注意这个backtracking就是自己调用自己,实现递归
141+
如此我们才遍历完图中的这棵树
85142

86-
一些同学对递归操作本来就不熟练,递归上面又加上一个for循环,可能就更迷糊了, 我来给大家捋顺一下。
143+
那么for循环每次就是从startIndex开始遍历,然后用path保存每次遍历到的节点。
87144

88-
这个backtracking 其实就是向树的叶子节点方向遍历, for循环可以理解是横向遍历,backtracking 就是纵向遍历,这样就把这棵树全遍历完了。
145+
代码如下:
89146

90-
那么backtracking就是一直往深处遍历,总会遇到叶子节点,遇到了叶子节点,就要返回,那么backtracking的下面部分就是回溯的操作了,撤销本次处理的结果。
147+
```
148+
for (int i = startIndex; i <= n; i++) {
149+
path.push_back(i); // 处理节点
150+
backtracking(n, k, i + 1); // 注意下一层搜索要从i+1开始
151+
path.pop_back(); // 回溯,撤销处理的节点
152+
}
153+
```
91154

92-
分析完模板,本题代码如下
155+
关键地方都讲完了,组合问题C++完整代码如下
93156

94-
# C++ 代码
95157

96158
```
97159
class Solution {
@@ -103,16 +165,16 @@ private:
103165
result.push_back(path);
104166
return;
105167
}
106-
// 这个for循环有讲究,组合的时候 要用startIndex,排列的时候就要从0开始
107168
for (int i = startIndex; i <= n; i++) {
108169
path.push_back(i); // 处理节点
109170
backtracking(n, k, i + 1);
110171
path.pop_back(); // 回溯,撤销处理的节点
111172
}
112173
}
113174
public:
114-
115175
vector<vector<int>> combine(int n, int k) {
176+
result.clear(); // 可以不写
177+
path.clear(); // 可以不写
116178
backtracking(n, k, 1);
117179
return result;
118180
}
@@ -145,7 +207,7 @@ for (int i = startIndex; i <= n; i++)
145207
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++)
146208
```
147209

148-
整体代码如下
210+
优化后整体代码如下
149211

150212
```
151213
class Solution {
@@ -157,7 +219,6 @@ private:
157219
result.push_back(path);
158220
return;
159221
}
160-
// 这个for循环有讲究,组合的时候 要用startIndex,排列的时候就要从0开始
161222
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
162223
path.push_back(i); // 处理节点
163224
backtracking(n, k, i + 1);
@@ -174,6 +235,3 @@ public:
174235
```
175236

176237

177-
178-
# 观后感
179-
我来写一下观后感: 很厉害,转化成从集合中选取子集的问题,可选择的范围随着选择的进行而限缩,于是做剪枝,调整可选择的范围。 每一次的递归是层叠嵌套的关系,可以用于解决多层嵌套循环的问题。 每一层递归中,尽量节省循环次数,这样在后续的递归调用中,节省下来的循环会被以至少指数等级放大。

problems/0104.二叉树的最大深度.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ public:
174174
int depth = 0;
175175
while (!que.empty()) {
176176
int size = que.size();
177+
vector<int> vec;
177178
depth++; // 记录深度
178179
for (int i = 0; i < size; i++) {
179180
Node* node = que.front();
180181
que.pop();
181-
for (int i = 0; i < node->children.size(); i++) {
182-
if (node->children[i]) que.push(node->children[i]);
182+
for (int j = 0; j < node->children.size(); j++) {
183+
if (node->children[j]) que.push(node->children[j]);
183184
}
184185
}
185186
}

problems/0559.N叉树的最大深度.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ public:
3131
while (!que.empty()) {
3232
int size = que.size();
3333
vector<int> vec;
34-
depth++;
34+
depth++; // 记录深度
3535
for (int i = 0; i < size; i++) {
3636
Node* node = que.front();
3737
que.pop();
38-
for (int i = 0; i < node->children.size(); i++) {
39-
if (node->children[i]) que.push(node->children[i]);
38+
for (int j = 0; j < node->children.size(); j++) {
39+
if (node->children[j]) que.push(node->children[j]);
4040
}
4141
}
4242
}

0 commit comments

Comments
 (0)