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

Commit 57d6ce8

Browse files
Update
1 parent 71d8678 commit 57d6ce8

15 files changed

+215
-33
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
* [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)
108108
* [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA)
109109
* [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)
110+
* [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
110111

111112

112113

@@ -264,6 +265,7 @@
264265
|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**广度优先搜索**|
265266
|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**广度优先搜索**|
266267
|[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**|
268+
|[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**|
267269
|[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**|
268270
|[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) ||中等|**递归** **迭代/栈**|
269271
|[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) ||困难|**递归** **迭代/栈**|

pics/42.接雨水1.png

84.4 KB
Loading

pics/42.接雨水2.png

83.7 KB
Loading

pics/42.接雨水3.png

46.4 KB
Loading

pics/654.最大二叉树.png

25.9 KB
Loading

problems/0017.电话号码的字母组合.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,8 @@ public:
9797
};
9898
```
9999

100+
# 拓展
101+
102+
请问为什么 getCombinations(const string& digits, int index, const string& s)函数里的string& s 前要加const,不加的报错
103+
100104
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。

problems/0042.接雨水.md

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,103 @@
66
// 找左面最大的, 找右边最大的,找左右边际的时候容易迷糊。我已开始还找左大于右的。 (还不够)
77
// 每次记录单条,不要记录整个面积
88

9-
# C++代码
9+
## 暴力解法
10+
11+
这道题目暴力解法并不简单,我们来看一下思路。
12+
13+
首先要明确,要按照行来计算,还是按照列来计算。如图所示:
14+
15+
<img src='../pics/42.接雨水2.png' width=600> </img></div>
16+
17+
<img src='../pics/42.接雨水1.png' width=600> </img></div>
18+
19+
一些同学在实现暴力解法的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
20+
21+
我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。
22+
23+
首先,**如果按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。**
24+
25+
可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
26+
27+
这句话可以有点绕,来举一个理解,例如求列4的雨水高度,如图:
28+
29+
<img src='../pics/42.接雨水3.png' width=600> </img></div>
30+
31+
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
32+
33+
列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。
34+
35+
列4 柱子的高度为1(以下用height表示)
36+
37+
那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。
38+
39+
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。
40+
41+
此时求出了列4的雨水体积。
42+
43+
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
44+
45+
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
46+
```
47+
for (int i = 0; i < height.size(); i++) {
48+
// 第一个柱子和最后一个柱子不接雨水
49+
if (i == 0 || i == height.size() - 1) continue;
50+
}
51+
```
52+
53+
在for循环中求左右两边最高柱子,代码如下:
54+
55+
```
56+
int rHeight = height[i]; // 记录右边柱子的最高高度
57+
int lHeight = height[i]; // 记录左边柱子的最高高度
58+
for (int r = i + 1; r < height.size(); r++) {
59+
if (height[r] > rHeight) rHeight = height[r];
60+
}
61+
for (int l = i - 1; l >= 0; l--) {
62+
if (height[l] > lHeight) lHeight = height[l];
63+
}
64+
```
65+
66+
最后,计算该列的雨水高度,代码如下:
67+
68+
```
69+
int h = min(lHeight, rHeight) - height[i];
70+
if (h > 0) sum += h; // 注意只有h大于零的时候,在统计到总和中
71+
```
72+
73+
整体代码如下:
1074

11-
按照列来
1275
```
1376
class Solution {
1477
public:
1578
int trap(vector<int>& height) {
1679
int sum = 0;
17-
// for (int i = 0; i < height.size(); i++) {
18-
// cout << height[i] << " ";
19-
// }
20-
// cout << endl;
2180
for (int i = 0; i < height.size(); i++) {
81+
// 第一个柱子和最后一个柱子不接雨水
2282
if (i == 0 || i == height.size() - 1) continue;
2383
24-
int lIndex, rIndex;
25-
int rValue = height[i];
26-
int lValue = height[i];
84+
int rHeight = height[i]; // 记录右边柱子的最高高度
85+
int lHeight = height[i]; // 记录左边柱子的最高高度
2786
for (int r = i + 1; r < height.size(); r++) {
28-
if (height[r] > rValue) {
29-
rValue = height[r];
30-
rIndex = r;
31-
}
87+
if (height[r] > rHeight) rHeight = height[r];
3288
}
3389
for (int l = i - 1; l >= 0; l--) {
34-
if (height[l] > lValue) {
35-
lValue = height[l];
36-
lIndex = l;
37-
}
90+
if (height[l] > lHeight) lHeight = height[l];
3891
}
39-
int h = min(lValue, rValue) - height[i];
40-
// 我为啥要算 (rIndex - lIndex + 1);就是按照行 按照列 区分不清啊
92+
int h = min(lHeight, rHeight) - height[i];
4193
if (h > 0) sum += h;
4294
}
4395
return sum;
4496
}
4597
};
4698
```
99+
100+
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2)。
101+
空间复杂度为O(1)。
102+
103+
104+
# 单调栈
105+
106+
单调栈究竟如何做呢,得画一个图,不太好理解
107+
108+
按照列来算的,遇到相同的怎么办。

problems/0141.环形链表.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ fast和slow各自再走一步, fast和slow就相遇了
1919

2020
这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。
2121

22+
动画如下:
23+
24+
25+
<img src='../video/141.环形链表.gif' width=600> </img></div>
26+
2227

2328
## C++代码如下
2429

problems/0142.环形链表II.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ fast和slow各自再走一步, fast和slow就相遇了
4343

4444
这是因为fast是走两步,slow是走一步,**其实相对于slow来说,fast是一个节点一个节点的靠近slow的**,所以fast一定可以和slow重合。
4545

46+
动画如下:
47+
48+
49+
<video src='../video/142.环形链表II1.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
50+
4651

4752
## 如果有环,如何找到这个环的入口
4853

@@ -83,6 +88,11 @@ fast指针走过的节点数:` x + y + n (y + z)`,n为fast指针在环内走
8388

8489
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
8590

91+
动画如下:
92+
93+
<video src='../video/142.环形链表II.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
94+
95+
8696
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
8797

8898
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

problems/0654.最大二叉树.md

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
## 题目地址
22
https://leetcode-cn.com/problems/maximum-binary-tree/
33

4+
> 用数组构建二叉树都是一样的套路
5+
6+
# 654.最大二叉树
7+
8+
给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:
9+
10+
* 二叉树的根是数组中的最大元素。
11+
* 左子树是通过数组中最大值左边部分构造出的最大二叉树。
12+
* 右子树是通过数组中最大值右边部分构造出的最大二叉树。
13+
14+
通过给定的数组构建最大二叉树,并且输出这个树的根节点。
15+
16+
示例 :
17+
18+
<img src='../pics/654.最大二叉树.png' width=600> </img></div>
19+
20+
提示:
21+
22+
给定的数组的大小在 [1, 1000] 之间。
23+
424
## 思路
525

626
最大二叉树的构建过程如下:
727

8-
<video src='../video/654.最大二叉树.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
28+
<img src='../video/654.最大二叉树.gif' width=600> </img></div>
929

10-
典型的递归问题,依然按照递归三部曲来分析:
30+
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
1131

12-
* 确定递归函数的参数和返回值
13-
* 确定终止条件
14-
* 确定单层递归的逻辑
15-
16-
### 确定递归函数的参数和返回值
32+
* 确定递归函数的参数和返回值
1733

1834
参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
1935

@@ -23,9 +39,11 @@ https://leetcode-cn.com/problems/maximum-binary-tree/
2339
TreeNode* constructMaximumBinaryTree(vector<int>& nums)
2440
2541
```
26-
### 确定终止条件
42+
* 确定终止条件
43+
44+
题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了。
2745

28-
题目中说了输入的数组大小一定是大于等于1的,所以我们不用考虑小于1的情况,那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了具体节点了,那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。
46+
那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候,构造了一个新的节点,并返回。
2947

3048
代码如下:
3149

@@ -36,11 +54,12 @@ if (nums.size() == 1) {
3654
    return node;
3755
}
3856
```
39-
### 确定单层递归的逻辑
57+
58+
* 确定单层递归的逻辑
4059

4160
这里有三步工作
4261

43-
1. 先要找到数组中最大的值和对应的下表, 最大的值就是根节点
62+
1. 先要找到数组中最大的值和对应的下表, 最大的值构造根节点,下表用来下一步分割数组。
4463

4564
代码如下:
4665
```
@@ -82,8 +101,6 @@ if (maxValueIndex < (nums.size() - 1)) {
82101
```
83102
这样我们就分析完了,整体代码如下:(详细注释)
84103

85-
## C++代码
86-
87104
```
88105
class Solution {
89106
public:
@@ -105,7 +122,7 @@ public:
105122
node->val = maxValue;
106123
// 最大值所在的下表左区间 构造左子树
107124
if (maxValueIndex > 0) {
108-
vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
125+
vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
109126
node->left = constructMaximumBinaryTree(newVec);
110127
}
111128
// 最大值所在的下表右区间 构造右子树
@@ -117,4 +134,86 @@ public:
117134
}
118135
};
119136
```
137+
138+
以上代码比较冗余,效率也不高,每次还要切割的时候每次都要定义新的vector(也就是数组),但逻辑比较清晰。
139+
140+
和文章[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中一样的优化思路,就是每次分隔不用定义新的数组,而是通过下表索引直接在原数组上操作。
141+
142+
优化后代码如下:
143+
144+
```
145+
class Solution {
146+
private:
147+
// 在左闭右开区间[left, right),构造二叉树
148+
TreeNode* traversal(vector<int>& nums, int left, int right) {
149+
if (left >= right) return nullptr;
150+
151+
// 分割点下表:maxValueIndex
152+
int maxValueIndex = left;
153+
for (int i = left + 1; i < right; ++i) {
154+
if (nums[i] > nums[maxValueIndex]) maxValueIndex = i;
155+
}
156+
157+
TreeNode* root = new TreeNode(nums[maxValueIndex]);
158+
159+
// 左闭右开:[left, maxValueIndex)
160+
root->left = traversal(nums, left, maxValueIndex);
161+
162+
// 左闭右开:[maxValueIndex + 1, right)
163+
root->right = traversal(nums, maxValueIndex + 1, right);
164+
165+
return root;
166+
}
167+
public:
168+
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
169+
return traversal(nums, 0, nums.size());
170+
}
171+
};
172+
```
173+
174+
# 拓展
175+
176+
可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空**
177+
178+
第一版递归过程:(加了if判断,为了不让空节点进入递归)
179+
```
180+
181+
if (maxValueIndex > 0) { // 这里加了判断是为了不让空节点进入递归
182+
vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
183+
node->left = constructMaximumBinaryTree(newVec);
184+
}
185+
186+
if (maxValueIndex < (nums.size() - 1)) { // 这里加了判断是为了不让空节点进入递归
187+
vector<int> newVec(nums.begin() + maxValueIndex + 1, nums.end());
188+
node->right = constructMaximumBinaryTree(newVec);
189+
}
190+
```
191+
192+
第二版递归过程: (如下代码就没有加if判断)
193+
194+
```
195+
root->left = traversal(nums, left, maxValueIndex);
196+
197+
root->right = traversal(nums, maxValueIndex + 1, right);
198+
```
199+
200+
第二版代码是允许空节点进入递归,所以没有加if判断,当然终止条件也要有相应的改变。
201+
202+
第一版终止条件,是遇到叶子节点就终止,因为空节点不会进入递归。
203+
204+
第二版相应的终止条件,是遇到空节点,也就是数组区间为0,就终止了。
205+
206+
207+
# 总结
208+
209+
这道题目其实和 [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 是一个思路,比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 还简单一些。
210+
211+
**注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。**
212+
213+
一些同学也会疑惑,什么时候递归函数前面加if,什么时候不加if,这个问题我在最后也给出了解释。
214+
215+
其实就是不同代码风格的实现,**一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。**
216+
217+
218+
120219
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。

video/141.环形链表.gif

1.82 MB
Loading

video/141.环形链表.mp4

182 KB
Binary file not shown.

video/142.环形链表II.mp4

1.61 MB
Binary file not shown.

video/142.环形链表II1.mp4

1.18 MB
Binary file not shown.

video/654.最大二叉树.gif

724 KB
Loading

0 commit comments

Comments
 (0)