mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 23:28:29 +08:00
Update
This commit is contained in:
@ -79,6 +79,7 @@
|
||||
* [栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)
|
||||
* [栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng)
|
||||
* [栈与队列:总结篇!](https://mp.weixin.qq.com/s/xBcHyvHlWq4P13fzxEtkPg)
|
||||
|
||||
* 二叉树
|
||||
* [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)
|
||||
|
||||
|
@ -27,6 +27,11 @@ https://leetcode-cn.com/problems/implement-strstr/
|
||||
|
||||
本题是KMP 经典题目。
|
||||
|
||||
以下文字如果看不进去,可以看我的B站视频:
|
||||
|
||||
* [帮你把KMP算法学个通透!B站(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd/)
|
||||
* [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx)
|
||||
|
||||
KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。**
|
||||
|
||||
本篇将以如下顺序来讲解KMP,
|
||||
|
81
problems/0112.路径总和.md
Normal file
81
problems/0112.路径总和.md
Normal file
@ -0,0 +1,81 @@
|
||||
## 题目地址
|
||||
|
||||
## 思路
|
||||
// 遍历单条边,还是遍历整个树,取最优数值!!
|
||||
// 对啊,用sum来减法啊,免得多定义一个变量
|
||||
|
||||
## C++
|
||||
|
||||
贼粗糙的写法
|
||||
|
||||
深度优先遍历
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
bool traversal(TreeNode* cur, int count) {
|
||||
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
|
||||
if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回
|
||||
|
||||
if (cur->left) { // 左
|
||||
// 遇到叶子节点返回true,则直接返回true
|
||||
if (traversal(cur->left, count - cur->left->val)) return true;
|
||||
}
|
||||
if (cur->right) { // 右
|
||||
// 遇到叶子节点返回true,则直接返回true
|
||||
if (traversal(cur->right, count - cur->right->val)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
bool hasPathSum(TreeNode* root, int sum) {
|
||||
if (root == NULL) return false;
|
||||
return traversal(root, sum - root->val);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
其实本题一定是有回溯的,没有回溯,如果后撤重新找另一条路径呢,但是貌似以上代码中,**大家貌似没有感受到回溯,那是因为回溯在代码里隐藏起来了。**
|
||||
|
||||
隐藏在`traversal(cur->left, count - cur->left->val)`这里, 因为把`count - cur->left->val` 直接作为参数传进去,函数结束,count自然恢复到原先的数值了。
|
||||
|
||||
为了把回溯的过程体现出来,将`if (traversal(cur->left, count - cur->left->val)) return true;` 改为如下代码:
|
||||
|
||||
```
|
||||
if (cur->left) { // 左
|
||||
count -= cur->left->val; // 递归,处理节点;
|
||||
if (traversal(cur->left, count)) return true;
|
||||
count += cur->left->val; // 回溯,撤销处理结果
|
||||
}
|
||||
```
|
||||
|
||||
这样大家就能感受到回溯了,整体回溯代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
bool traversal(TreeNode* cur, int count) {
|
||||
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
|
||||
if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回
|
||||
|
||||
if (cur->left) { // 左
|
||||
count -= cur->left->val; // 递归,处理节点;
|
||||
if (traversal(cur->left, count)) return true;
|
||||
count += cur->left->val; // 回溯,撤销处理结果
|
||||
}
|
||||
if (cur->right) { // 右
|
||||
count -= cur->right->val; // 递归,处理节点;
|
||||
if (traversal(cur->right, count)) return true;
|
||||
count += cur->right->val; // 回溯,撤销处理结果
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
bool hasPathSum(TreeNode* root, int sum) {
|
||||
if (root == NULL) return false;
|
||||
return traversal(root, sum - root->val);
|
||||
}
|
||||
};
|
||||
```
|
@ -26,6 +26,12 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/
|
||||
|
||||
这又是一道标准的KMP的题目。
|
||||
|
||||
如果KMP还不够了解,可以看我的B站:
|
||||
|
||||
* [帮你把KMP算法学个通透!B站(理论篇)](https://www.bilibili.com/video/BV1PD4y1o7nd/)
|
||||
* [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx)
|
||||
|
||||
|
||||
如果KMP还不够了解,可以看我的这个视频[帮你把KMP算法学个通透!B站](https://www.bilibili.com/video/BV1PD4y1o7nd/)
|
||||
|
||||
我们在[字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。
|
||||
|
@ -3,7 +3,121 @@ https://leetcode-cn.com/problems/merge-two-binary-trees/
|
||||
|
||||
## 思路
|
||||
|
||||
四种写法,总有一款适合你,其实这道题目迭代法实现是比较困难的,大家可以试一试,是一道不错的面试进阶题目。
|
||||
相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢?
|
||||
|
||||
其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。
|
||||
|
||||
那么前中后序应该使用哪种遍历呢?
|
||||
|
||||
**本题使用哪种遍历都是可以的!**
|
||||
|
||||
我们下面以前序遍历为例。
|
||||
|
||||
动画如下:
|
||||
|
||||
|
||||
<img src='../video/617.合并二叉树.gif' width=600> </img></div>
|
||||
|
||||
那么我们来按照递归三部曲来解决:
|
||||
|
||||
1. **确定递归函数的参数和返回值:**
|
||||
首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
|
||||
```
|
||||
|
||||
2. **确定终止条件:**
|
||||
|
||||
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了啊(如果t2也为NULL也无所谓)。
|
||||
|
||||
反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓)。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
|
||||
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
|
||||
```
|
||||
|
||||
|
||||
3. **确定单层递归的逻辑:**
|
||||
|
||||
单层递归的逻辑就比较好些了,这里我们用重复利用一下t1这个树,t1就是合并之后树的根节点(所谓的修改了元数据的结构)。
|
||||
|
||||
那么单层递归中,就要把两棵树的元素加到一起。
|
||||
```
|
||||
t1->val += t2->val;
|
||||
```
|
||||
|
||||
那么此时t1 的左子树 应该是 合并 t1左子树 t2左子树之后的左子树,t1 的右子树 应该是 合并 t1右子树 t2右子树之后的右子树。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
t1->left = mergeTrees(t1->left, t2->left);
|
||||
t1->right = mergeTrees(t1->right, t2->right);
|
||||
return t1;
|
||||
```
|
||||
|
||||
此时前序遍历,修改原输入树结构的完整代码就写出来了,如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
|
||||
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
|
||||
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
|
||||
// 修改了t1的数值和结构
|
||||
t1->val += t2->val; // 中
|
||||
t1->left = mergeTrees(t1->left, t2->left); // 左
|
||||
t1->right = mergeTrees(t1->right, t2->right); // 右
|
||||
return t1;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
那么中序遍历可不可以呢,也是可以的,代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
|
||||
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
|
||||
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
|
||||
// 修改了t1的数值和结构
|
||||
t1->left = mergeTrees(t1->left, t2->left); // 左
|
||||
t1->val += t2->val; // 中
|
||||
t1->right = mergeTrees(t1->right, t2->right); // 右
|
||||
return t1;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
后序遍历呢,依然可以,代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
|
||||
if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
|
||||
if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
|
||||
// 修改了t1的数值和结构
|
||||
t1->left = mergeTrees(t1->left, t2->left); // 左
|
||||
t1->right = mergeTrees(t1->right, t2->right); // 右
|
||||
t1->val += t2->val; // 中
|
||||
return t1;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**但是前序遍历是最好理解的,我建议大家用前序遍历来做就OK。**
|
||||
|
||||
**那么如下还总结了四种方法,递归的方式均使用了前序遍历,此时大家应该知道了,以下每一种递归的方法都可以换成中序和后序遍历,所以本题的解法是很多的。**
|
||||
|
||||
**其实这道题目迭代法实现是比较困难的,大家可以试一试,是一道不错的面试进阶题目。**
|
||||
|
||||
四种写法如下:
|
||||
|
||||
@ -16,7 +130,7 @@ https://leetcode-cn.com/problems/merge-two-binary-trees/
|
||||
|
||||
### 递归
|
||||
|
||||
修改了输入树的结构
|
||||
修改了输入树的结构,前序遍历
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -32,7 +146,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
不修改输入树的结构
|
||||
不修改输入树的结构,前序遍历
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -51,6 +165,7 @@ public:
|
||||
|
||||
一波指针的操作,自己写的野路子
|
||||
想要更改二叉树的值,应该传入指向指针的指针, 如果process(t1, t2);这么写的话,其实只是传入的一个int型的指针,并没有传入地址,要传入指向指针的指针才能完成对t1的修改。
|
||||
(前序遍历)
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -77,7 +192,7 @@ public:
|
||||
```
|
||||
### 迭代
|
||||
|
||||
这应该是最简单直观的迭代法了
|
||||
这应该是最简单直观的迭代法了,模拟的层序遍历。
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
|
@ -12,13 +12,13 @@ https://leetcode-cn.com/problems/binary-tree-cameras/
|
||||
|
||||
我们之前做动态规划的时候,只要最难的地方在于确定状态转移方程,至于遍历方式无非就是在数组或者二维数组上。
|
||||
|
||||
**而本题,不仅要确定状态转移方式,而且要在树上进行推导,所以难度就上来了,一些同学知道这道题目难,但其实说不上难点究竟在哪。**
|
||||
**本题并不是动态规划,其本质是贪心,但我们要确定状态转移方式,而且要在树上进行推导,所以难度就上来了,一些同学知道这道题目难,但其实说不上难点究竟在哪。**
|
||||
|
||||
1. 需要确定遍历方式
|
||||
|
||||
首先先确定遍历方式,才能确定转移方程,那么该如何遍历呢?
|
||||
|
||||
在安排选择摄像头的位置的时候,**我们要从底向上进行推导,因为尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的**
|
||||
在安排选择摄像头的位置的时候,**我们要从底向上进行推导,因为尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的**,这也是本道贪心的原理所在!
|
||||
|
||||
如何从低向上推导呢?
|
||||
|
||||
@ -61,7 +61,9 @@ https://leetcode-cn.com/problems/binary-tree-cameras/
|
||||
|
||||
大家应该找不出第四个节点的状态了。
|
||||
|
||||
那么问题来了,空节点究竟是哪一种状态呢? 空节点表示无覆盖? 表示有摄像头?还是有覆盖呢?
|
||||
**一些同学可能会想有没有第四种状态:本节点无摄像头,其实无摄像头就是 无覆盖 或者 有覆盖的状态,所以一共还是三个状态。**
|
||||
|
||||
**那么问题来了,空节点究竟是哪一种状态呢? 空节点表示无覆盖? 表示有摄像头?还是有覆盖呢? **
|
||||
|
||||
回归本质,为了让摄像头数量最少,我们要尽量让叶子节点的父节点安装摄像头,这样才能摄像头的数量最少。
|
||||
|
||||
@ -141,7 +143,7 @@ left == 1 && right == 1 左右节点都有摄像头
|
||||
|
||||
这种情况也是大多数同学容易迷惑的情况。
|
||||
|
||||
4. 情况4
|
||||
4. 情况4:头结点没有覆盖
|
||||
|
||||
以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如图:
|
||||
|
||||
|
BIN
video/617.合并二叉树.gif
Normal file
BIN
video/617.合并二叉树.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
BIN
video/二叉树中序遍历(迭代法).gif
Normal file
BIN
video/二叉树中序遍历(迭代法).gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 MiB |
BIN
video/二叉树中序遍历(迭代法).mp4
Normal file
BIN
video/二叉树中序遍历(迭代法).mp4
Normal file
Binary file not shown.
BIN
video/二叉树前序遍历(迭代法).gif
Normal file
BIN
video/二叉树前序遍历(迭代法).gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
BIN
video/二叉树前序遍历(迭代法).mp4
Normal file
BIN
video/二叉树前序遍历(迭代法).mp4
Normal file
Binary file not shown.
Reference in New Issue
Block a user