This commit is contained in:
youngyangyang04
2020-10-10 12:21:05 +08:00
parent 71d8678c9a
commit 57d6ce8e56
15 changed files with 215 additions and 33 deletions

View File

@ -107,6 +107,7 @@
* [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA) * [二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)
* [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) * [二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA)
* [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw) * [二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)
* [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
@ -264,6 +265,7 @@
|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**广度优先搜索**| |[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**广度优先搜索**|
|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**广度优先搜索**| |[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**广度优先搜索**|
|[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**| |[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**|
|[0141.环形链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0141.环形链表.md) |链表 |简单|**快慢指针/双指针**|
|[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**| |[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**|
|[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**| |[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**|
|[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**| |[0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) |树 |困难|**递归** **迭代/栈**|

BIN
pics/42.接雨水1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
pics/42.接雨水2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
pics/42.接雨水3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

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

View File

@ -6,41 +6,103 @@
// 找左面最大的, 找右边最大的,找左右边际的时候容易迷糊。我已开始还找左大于右的。 (还不够) // 找左面最大的, 找右边最大的,找左右边际的时候容易迷糊。我已开始还找左大于右的。 (还不够)
// 每次记录单条,不要记录整个面积 // 每次记录单条,不要记录整个面积
# C++代码 ## 暴力解法
这道题目暴力解法并不简单,我们来看一下思路。
首先要明确,要按照行来计算,还是按照列来计算。如图所示:
<img src='../pics/42.接雨水2.png' width=600> </img></div>
<img src='../pics/42.接雨水1.png' width=600> </img></div>
一些同学在实现暴力解法的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
我个人倾向于按照列来计算,比较容易理解,接下来看一下按照列如何计算。
首先,**如果按照列来计算的话宽度一定是1了我们再把每一列的雨水的高度求出来就可以了。**
可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度。
这句话可以有点绕来举一个理解例如求列4的雨水高度如图
<img src='../pics/42.接雨水3.png' width=600> </img></div>
列4 左侧最高的柱子是列3高度为2以下用lHeight表示
列4 右侧最高的柱子是列7高度为3以下用rHeight表示
列4 柱子的高度为1以下用height表示
那么列4的雨水高度为 列3和列7的高度最小值减列4高度 min(lHeight, rHeight) - height。
列4的雨水高度求出来了宽度为1相乘就是列4的雨水体积了。
此时求出了列4的雨水体积。
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。
首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:
```
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue;
}
```
在for循环中求左右两边最高柱子代码如下
```
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i + 1; r < height.size(); r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i - 1; l >= 0; l--) {
if (height[l] > lHeight) lHeight = height[l];
}
```
最后,计算该列的雨水高度,代码如下:
```
int h = min(lHeight, rHeight) - height[i];
if (h > 0) sum += h; // 注意只有h大于零的时候在统计到总和中
```
整体代码如下:
按照列来
``` ```
class Solution { class Solution {
public: public:
int trap(vector<int>& height) { int trap(vector<int>& height) {
int sum = 0; int sum = 0;
// for (int i = 0; i < height.size(); i++) {
// cout << height[i] << " ";
// }
// cout << endl;
for (int i = 0; i < height.size(); i++) { for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i == 0 || i == height.size() - 1) continue; if (i == 0 || i == height.size() - 1) continue;
int lIndex, rIndex; int rHeight = height[i]; // 记录右边柱子的最高高度
int rValue = height[i]; int lHeight = height[i]; // 记录左边柱子的最高高度
int lValue = height[i];
for (int r = i + 1; r < height.size(); r++) { for (int r = i + 1; r < height.size(); r++) {
if (height[r] > rValue) { if (height[r] > rHeight) rHeight = height[r];
rValue = height[r];
rIndex = r;
}
} }
for (int l = i - 1; l >= 0; l--) { for (int l = i - 1; l >= 0; l--) {
if (height[l] > lValue) { if (height[l] > lHeight) lHeight = height[l];
lValue = height[l];
lIndex = l;
}
} }
int h = min(lValue, rValue) - height[i]; int h = min(lHeight, rHeight) - height[i];
// 我为啥要算 (rIndex - lIndex + 1);就是按照行 按照列 区分不清啊
if (h > 0) sum += h; if (h > 0) sum += h;
} }
return sum; return sum;
} }
}; };
``` ```
因为每次遍历列的时候还要向两边寻找最高的列所以时间复杂度为O(n^2)。
空间复杂度为O(1)。
# 单调栈
单调栈究竟如何做呢,得画一个图,不太好理解
按照列来算的,遇到相同的怎么办。

View File

@ -19,6 +19,11 @@ fast和slow各自再走一步 fast和slow就相遇了
这是因为fast是走两步slow是走一步**其实相对于slow来说fast是一个节点一个节点的靠近slow的**所以fast一定可以和slow重合。 这是因为fast是走两步slow是走一步**其实相对于slow来说fast是一个节点一个节点的靠近slow的**所以fast一定可以和slow重合。
动画如下:
<img src='../video/141.环形链表.gif' width=600> </img></div>
## C++代码如下 ## C++代码如下

View File

@ -43,6 +43,11 @@ fast和slow各自再走一步 fast和slow就相遇了
这是因为fast是走两步slow是走一步**其实相对于slow来说fast是一个节点一个节点的靠近slow的**所以fast一定可以和slow重合。 这是因为fast是走两步slow是走一步**其实相对于slow来说fast是一个节点一个节点的靠近slow的**所以fast一定可以和slow重合。
动画如下:
<video src='../video/142.环形链表II1.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
## 如果有环,如何找到这个环的入口 ## 如果有环,如何找到这个环的入口
@ -83,6 +88,11 @@ fast指针走过的节点数` x + y + n (y + z)`n为fast指针在环内走
让index1和index2同时移动每次移动一个节点 那么他们相遇的地方就是 环形入口的节点。 让index1和index2同时移动每次移动一个节点 那么他们相遇的地方就是 环形入口的节点。
动画如下:
<video src='../video/142.环形链表II.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
那么 n如果大于1是什么情况呢就是fast指针在环形转n圈之后才遇到 slow指针。 那么 n如果大于1是什么情况呢就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点只不过index1 指针在环里 多转了(n-1)圈然后再遇到index2相遇点依然是环形的入口节点。 其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点只不过index1 指针在环里 多转了(n-1)圈然后再遇到index2相遇点依然是环形的入口节点。

View File

@ -1,19 +1,35 @@
## 题目地址 ## 题目地址
https://leetcode-cn.com/problems/maximum-binary-tree/ https://leetcode-cn.com/problems/maximum-binary-tree/
> 用数组构建二叉树都是一样的套路
# 654.最大二叉树
给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下:
* 二叉树的根是数组中的最大元素。
* 左子树是通过数组中最大值左边部分构造出的最大二叉树。
* 右子树是通过数组中最大值右边部分构造出的最大二叉树。
通过给定的数组构建最大二叉树,并且输出这个树的根节点。
示例
<img src='../pics/654.最大二叉树.png' width=600> </img></div>
提示:
给定的数组的大小在 [1, 1000] 之间。
## 思路 ## 思路
最大二叉树的构建过程如下: 最大二叉树的构建过程如下:
<video src='../video/654.最大二叉树.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div> <img src='../video/654.最大二叉树.gif' width=600> </img></div>
典型的递归问题,依然按照递归三部曲来分析: 构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
* 确定递归函数的参数和返回值 * 确定递归函数的参数和返回值
* 确定终止条件
* 确定单层递归的逻辑
### 确定递归函数的参数和返回值
参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。 参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
@ -23,9 +39,11 @@ https://leetcode-cn.com/problems/maximum-binary-tree/
TreeNode* constructMaximumBinaryTree(vector<int>& nums) TreeNode* constructMaximumBinaryTree(vector<int>& nums)
``` ```
### 确定终止条件 * 确定终止条件
题目中说了输入的数组大小一定是大于等于1的所以我们不用考虑小于1的情况那么当递归遍历的时候如果传入的数组大小为1说明遍历到了具体节点了,那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候构造了一个新的节点并返回 题目中说了输入的数组大小一定是大于等于1的所以我们不用考虑小于1的情况那么当递归遍历的时候如果传入的数组大小为1说明遍历到了叶子节点了
那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是1的时候构造了一个新的节点并返回。
代码如下: 代码如下:
@ -36,11 +54,12 @@ if (nums.size() == 1) {
    return node;     return node;
} }
``` ```
### 确定单层递归的逻辑
* 确定单层递归的逻辑
这里有三步工作 这里有三步工作
1. 先要找到数组中最大的值和对应的下表, 最大的值就是根节点 1. 先要找到数组中最大的值和对应的下表, 最大的值构造根节点,下表用来下一步分割数组。
代码如下: 代码如下:
``` ```
@ -82,8 +101,6 @@ if (maxValueIndex < (nums.size() - 1)) {
``` ```
这样我们就分析完了,整体代码如下:(详细注释) 这样我们就分析完了,整体代码如下:(详细注释)
## C++代码
``` ```
class Solution { class Solution {
public: public:
@ -105,7 +122,7 @@ public:
node->val = maxValue; node->val = maxValue;
// 最大值所在的下表左区间 构造左子树 // 最大值所在的下表左区间 构造左子树
if (maxValueIndex > 0) { if (maxValueIndex > 0) {
vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex); vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
node->left = constructMaximumBinaryTree(newVec); node->left = constructMaximumBinaryTree(newVec);
} }
// 最大值所在的下表右区间 构造右子树 // 最大值所在的下表右区间 构造右子树
@ -117,4 +134,86 @@ public:
} }
}; };
``` ```
以上代码比较冗余效率也不高每次还要切割的时候每次都要定义新的vector也就是数组但逻辑比较清晰。
和文章[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中一样的优化思路,就是每次分隔不用定义新的数组,而是通过下表索引直接在原数组上操作。
优化后代码如下:
```
class Solution {
private:
// 在左闭右开区间[left, right),构造二叉树
TreeNode* traversal(vector<int>& nums, int left, int right) {
if (left >= right) return nullptr;
// 分割点下表maxValueIndex
int maxValueIndex = left;
for (int i = left + 1; i < right; ++i) {
if (nums[i] > nums[maxValueIndex]) maxValueIndex = i;
}
TreeNode* root = new TreeNode(nums[maxValueIndex]);
// 左闭右开:[left, maxValueIndex)
root->left = traversal(nums, left, maxValueIndex);
// 左闭右开:[maxValueIndex + 1, right)
root->right = traversal(nums, maxValueIndex + 1, right);
return root;
}
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums, 0, nums.size());
}
};
```
# 拓展
可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空**
第一版递归过程加了if判断为了不让空节点进入递归
```
if (maxValueIndex > 0) { // 这里加了判断是为了不让空节点进入递归
vector<int> newVec(nums.begin(), nums.begin() + maxValueIndex);
node->left = constructMaximumBinaryTree(newVec);
}
if (maxValueIndex < (nums.size() - 1)) { // 这里加了判断是为了不让空节点进入递归
vector<int> newVec(nums.begin() + maxValueIndex + 1, nums.end());
node->right = constructMaximumBinaryTree(newVec);
}
```
第二版递归过程: 如下代码就没有加if判断
```
root->left = traversal(nums, left, maxValueIndex);
root->right = traversal(nums, maxValueIndex + 1, right);
```
第二版代码是允许空节点进入递归所以没有加if判断当然终止条件也要有相应的改变。
第一版终止条件,是遇到叶子节点就终止,因为空节点不会进入递归。
第二版相应的终止条件是遇到空节点也就是数组区间为0就终止了。
# 总结
这道题目其实和 [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 是一个思路,比[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) 还简单一些。
**注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。**
一些同学也会疑惑什么时候递归函数前面加if什么时候不加if这个问题我在最后也给出了解释。
其实就是不同代码风格的实现,**一般情况来说如果让空节点空指针进入递归就不加if如果不让空节点进入递归就加if限制一下 终止条件也会相应的调整。**
> 更多算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。 > 更多算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。

BIN
video/141.环形链表.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
video/141.环形链表.mp4 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB