This commit is contained in:
programmercarl
2022-09-29 11:04:11 +08:00
parent 721bab7636
commit 882defc255
8 changed files with 98 additions and 65 deletions

View File

@ -27,7 +27,7 @@
![106. 从中序与后序遍历序列构造二叉树1](https://img-blog.csdnimg.cn/20210203154316774.png) ![106. 从中序与后序遍历序列构造二叉树1](https://img-blog.csdnimg.cn/20210203154316774.png)
## 视频讲解 # 视频讲解
**《代码随想录》算法视频公开课:[坑很多!来看看你掉过几次坑 | LeetCode106.从中序与后序遍历序列构造二叉树](https://www.bilibili.com/video/BV1vW4y1i7dn),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 **《代码随想录》算法视频公开课:[坑很多!来看看你掉过几次坑 | LeetCode106.从中序与后序遍历序列构造二叉树](https://www.bilibili.com/video/BV1vW4y1i7dn),相信结合视频在看本篇题解,更有助于大家对本题的理解**。

View File

@ -68,7 +68,7 @@ fast和slow各自再走一步 fast和slow就相遇了
环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。
从相遇节点 再到环形入口节点节点数为 z。 如图所示: 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
![142环形链表2](https://img-blog.csdnimg.cn/20210318162938397.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220925103433.png)
那么相遇时: 那么相遇时:
slow指针走过的节点数为: `x + y` slow指针走过的节点数为: `x + y`

View File

@ -98,7 +98,7 @@ public:
st.pop(); st.pop();
if (tokens[i] == "+") st.push(num2 + num1); if (tokens[i] == "+") st.push(num2 + num1);
if (tokens[i] == "-") st.push(num2 - num1); if (tokens[i] == "-") st.push(num2 - num1);
if (tokens[i] == "*") st.push(num2 * num1); if (tokens[i] == "*") st.push((long)num2 * (long)num1); //力扣改了后台测试数据
if (tokens[i] == "/") st.push(num2 / num1); if (tokens[i] == "/") st.push(num2 / num1);
} else { } else {
st.push(stoi(tokens[i])); st.push(stoi(tokens[i]));

View File

@ -38,24 +38,32 @@
# 思路 # 思路
做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道利用回溯从底向上搜索遇到一个节点的左子树里有p右子树里有q那么当前节点就是最近公共祖先。 做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道利用回溯从底向上搜索遇到一个节点的左子树里有p右子树里有q那么当前节点就是最近公共祖先。
那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。 那么本题是二叉搜索树,二叉搜索树是有序的,那得好好利用一下这个特点。
在有序树里如果判断一个节点的左子树里有p右子树里有q呢 在有序树里如果判断一个节点的左子树里有p右子树里有q呢
其实只要从上到下遍历的时候cur节点是数值在[p, q]区间中则说明该节点cur就是最近公共祖先了。 因为是有序树,所有 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p
那么只要从上到下去遍历遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q p的公共祖先 那问题来了**一定是最近公共祖先吗**
如图我们从根节点搜索第一次遇到 cur节点是数值在[p, q]区间中 节点5此时可以说明 p q 一定分别存在于 节点 5的左子树和右子树中
![235.二叉搜索树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/20220926164214.png)
此时节点5是不是最近公共祖先 如果 从节点5继续向左遍历那么将错过成为q的祖先 如果从节点5继续向右遍历则错过成为p的祖先
所以当我们从上向下去递归遍历第一次遇到 cur节点是数值在[p, q]区间中那么cur就是 p和q的最近公共祖先
理解这一点本题就很好解了 理解这一点本题就很好解了
和[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。 而递归遍历顺序本题就不涉及到 前中后序了这里没有中节点的处理逻辑遍历顺序无所谓了)。
那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。
如图所示p为节点3q为节点5 如图所示p为节点3q为节点5
![235.二叉搜索树的最近公共祖先](https://img-blog.csdnimg.cn/20210204150858927.png) ![235.二叉搜索树的最近公共祖先2](https://code-thinking-1253855093.file.myqcloud.com/pics/20220926165141.png)
可以看出直接按照指定的方向就可以找到节点4为最近公共祖先而且不需要遍历整棵树找到结果直接返回 可以看出直接按照指定的方向就可以找到节点4为最近公共祖先而且不需要遍历整棵树找到结果直接返回

View File

@ -41,23 +41,29 @@
回溯啊,二叉树回溯的过程就是从低到上。 回溯啊,二叉树回溯的过程就是从低到上。
后序遍历就是天然的回溯过程,最先处理的一定是叶子节点。 后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
接下来就看如何判断一个节点是节点q和节点p的公共公共祖先呢。 接下来就看如何判断一个节点是节点q和节点p的公共公共祖先呢。
**首先最容易想到的一个情况如果找到一个节点发现左子树出现结点p右子树出现节点q或者 左子树出现结点q右子树出现节点p那么该节点就是节点p和q的最近公共祖先。** **首先最容易想到的一个情况如果找到一个节点发现左子树出现结点p右子树出现节点q或者 左子树出现结点q右子树出现节点p那么该节点就是节点p和q的最近公共祖先。** 即情况一:
**但是很多人容易忽略一个情况就是节点本身p(q)它拥有一个子孙节点q(p)。** ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220922173502.png)
使用后序遍历,回溯的过程,就是从低向上遍历节点,一旦发现满足第一种情况的节点,就是最近公共节点了。 判断逻辑是 如果递归遍历遇到q就将q返回遇到p 就将p返回那么如果 左右子树的返回值都不为空说明此时的中节点一定是q 和p 的最近祖先。
**但是如果p或者q本身就是最近公共祖先呢** 那么有录友可能疑惑,会不会左子树 遇到q 返回右子树也遇到q返回这样并没有找到 q 和p的最近祖先。
其实只需要找到一个节点是p或者q的时候直接返回当前节点无需继续递归子树 这么想的录友,要审题了,题目强调:**二叉树节点数值是不重复的,而且一定存在 q 和 p**
如果接下来的遍历中找到了后继节点满足第一种情况则修改返回值为后继节点,否则,继续返回已找到的节点即可。 **但是很多人容易忽略一个情况就是节点本身p(q)它拥有一个子孙节点q(p)。** 情况二:
为什么满足第一种情况的节点一定是p或q的后继节点呢?大家可以仔细思考一下。 ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220922173530.png)
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本省就是 公共祖先的情况。
这一点是很多录友容易忽略的,在下面的代码讲解中,可以在去体会。
递归三部曲: 递归三部曲:
@ -69,20 +75,24 @@
代码如下: 代码如下:
``` ```CPP
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
``` ```
* 确定终止条件 * 确定终止条件
如果找到了 节点p或者q或者遇到空节点返回。 遇到空的话,然后然后空,因为树都是空了,所以返回
那么我们来说一说,如果 root == q或者 root == p说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点处理逻辑,后下面讲解。
代码如下: 代码如下:
``` ```CPP
if (root == q || root == p || root == NULL) return root; if (root == q || root == p || root == NULL) return root;
``` ```
* 确定单层递归逻辑 * 确定单层递归逻辑
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。 值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
@ -93,7 +103,7 @@ if (root == q || root == p || root == NULL) return root;
搜索一条边的写法: 搜索一条边的写法:
``` ```CPP
if (递归函数(root->left)) return ; if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ; if (递归函数(root->right)) return ;
@ -101,10 +111,10 @@ if (递归函数(root->right)) return ;
搜索整个树写法: 搜索整个树写法:
``` ```CPP
left = 递归函数(root->left); left = 递归函数(root->left); // 左
right = 递归函数(root->right); right = 递归函数(root->right); // 右
left与right的逻辑处理; left与right的逻辑处理; // 中
``` ```
看出区别了没? 看出区别了没?
@ -123,10 +133,10 @@ left与right的逻辑处理;
因为在如下代码的后序遍历中如果想利用left和right做逻辑处理 不能立刻返回而是要等left与right逻辑处理完之后才能返回。 因为在如下代码的后序遍历中如果想利用left和right做逻辑处理 不能立刻返回而是要等left与right逻辑处理完之后才能返回。
``` ```CPP
left = 递归函数(root->left); left = 递归函数(root->left); // 左
right = 递归函数(root->right); right = 递归函数(root->right); // 右
left与right的逻辑处理; left与right的逻辑处理; // 中
``` ```
所以此时大家要知道我们要遍历整棵树。知道这一点,对本题就有一定深度的理解了。 所以此时大家要知道我们要遍历整棵树。知道这一点,对本题就有一定深度的理解了。
@ -134,7 +144,7 @@ left与right的逻辑处理;
那么先用left和right接住左子树和右子树的返回值代码如下 那么先用left和right接住左子树和右子树的返回值代码如下
``` ```CPP
TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q);

View File

@ -19,13 +19,18 @@
注意: 合并必须从两个树的根节点开始。 注意: 合并必须从两个树的根节点开始。
# 思路 # 视频讲解
**《代码随想录》算法视频公开课:[一起操作两个二叉树?有点懵!| LeetCode617.合并二叉树](https://www.bilibili.com/video/BV1m14y1Y7JK),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢? 相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢?
其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。 其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。
## 递归 ### 递归
二叉树使用递归,就要想使用前中后哪种遍历方式? 二叉树使用递归,就要想使用前中后哪种遍历方式?
@ -207,7 +212,7 @@ public:
}; };
``` ```
# 拓展 ## 拓展
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。 当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。
@ -239,7 +244,7 @@ public:
}; };
``` ```
# 总结 ## 总结
合并二叉树,也是二叉树操作的经典题目,如果没有接触过的话,其实并不简单,因为我们习惯了操作一个二叉树,一起操作两个二叉树,还会有点懵懵的。 合并二叉树,也是二叉树操作的经典题目,如果没有接触过的话,其实并不简单,因为我们习惯了操作一个二叉树,一起操作两个二叉树,还会有点懵懵的。
@ -250,10 +255,10 @@ public:
最后拓展中我给了一个操作指针的野路子大家随便看看就行了如果学习C++的话,可以在去研究研究。 最后拓展中我给了一个操作指针的野路子大家随便看看就行了如果学习C++的话,可以在去研究研究。
# 其他语言版本 ## 其他语言版本
## Java ### Java
```Java ```Java
class Solution { class Solution {
@ -346,7 +351,7 @@ class Solution {
} }
``` ```
## Python ### Python
**递归法 - 前序遍历** **递归法 - 前序遍历**
```python ```python
@ -409,7 +414,7 @@ class Solution:
return root1 return root1
``` ```
## Go ### Go
```go ```go
/** /**
@ -503,7 +508,7 @@ func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
} }
``` ```
## JavaScript ### JavaScript
> 递归法: > 递归法:
@ -583,11 +588,11 @@ var mergeTrees = function(root1, root2) {
``` ```
## TypeScript ### TypeScript
> 递归法: > 递归法:
```type ```typescript
function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode | null { function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode | null {
if (root1 === null) return root2; if (root1 === null) return root2;
if (root2 === null) return root1; if (root2 === null) return root1;
@ -631,7 +636,7 @@ function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode |
}; };
``` ```
## Scala ### Scala
递归: 递归:
```scala ```scala

View File

@ -25,7 +25,12 @@
给定的数组的大小在 [1, 1000] 之间。 给定的数组的大小在 [1, 1000] 之间。
# 思路 ## 视频讲解
**《代码随想录》算法视频公开课:[又是构造二叉树,又有很多坑!| LeetCode654.最大二叉树](https://www.bilibili.com/video/BV1MG411G7ox),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
最大二叉树的构建过程如下: 最大二叉树的构建过程如下:
@ -175,7 +180,7 @@ public:
}; };
``` ```
# 拓展 ## 拓展
可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空** 可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空**
@ -207,7 +212,7 @@ root->right = traversal(nums, maxValueIndex + 1, right);
第二版相应的终止条件是遇到空节点也就是数组区间为0就终止了。 第二版相应的终止条件是遇到空节点也就是数组区间为0就终止了。
# 总结 ## 总结
这道题目其实和 [二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 是一个思路,比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 还简单一些。 这道题目其实和 [二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 是一个思路,比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 还简单一些。
@ -218,10 +223,10 @@ root->right = traversal(nums, maxValueIndex + 1, right);
其实就是不同代码风格的实现,**一般情况来说如果让空节点空指针进入递归就不加if如果不让空节点进入递归就加if限制一下 终止条件也会相应的调整。** 其实就是不同代码风格的实现,**一般情况来说如果让空节点空指针进入递归就不加if如果不让空节点进入递归就加if限制一下 终止条件也会相应的调整。**
# 其他语言版本 ## 其他语言版本
## Java ### Java
```Java ```Java
class Solution { class Solution {
@ -253,7 +258,7 @@ class Solution {
} }
``` ```
## Python ### Python
```python ```python
class Solution: class Solution:
@ -300,7 +305,7 @@ class Solution:
return root return root
``` ```
## Go ### Go
```go ```go
@ -334,7 +339,7 @@ func findMax(nums []int) (index int){
} }
``` ```
## JavaScript ### JavaScript
```javascript ```javascript
/** /**
@ -371,7 +376,7 @@ var constructMaximumBinaryTree = function (nums) {
}; };
``` ```
## TypeScript ### TypeScript
> 新建数组法: > 新建数组法:
@ -419,7 +424,7 @@ function constructMaximumBinaryTree(nums: number[]): TreeNode | null {
## C ### C
```c ```c
struct TreeNode* traversal(int* nums, int left, int right) { struct TreeNode* traversal(int* nums, int left, int right) {
@ -450,7 +455,7 @@ struct TreeNode* constructMaximumBinaryTree(int* nums, int numsSize){
} }
``` ```
## Swift ### Swift
```swift ```swift
func constructMaximumBinaryTree(_ nums: inout [Int]) -> TreeNode? { func constructMaximumBinaryTree(_ nums: inout [Int]) -> TreeNode? {
return traversal(&nums, 0, nums.count) return traversal(&nums, 0, nums.count)
@ -476,7 +481,7 @@ func traversal(_ nums: inout [Int], _ left: Int, _ right: Int) -> TreeNode? {
} }
``` ```
## Scala ### Scala
```scala ```scala
object Solution { object Solution {

View File

@ -17,7 +17,12 @@
在上述示例中,如果要找的值是 5但因为没有节点值为 5我们应该返回 NULL。 在上述示例中,如果要找的值是 5但因为没有节点值为 5我们应该返回 NULL。
# 思路 # 视频讲解
**《代码随想录》算法视频公开课:[不愧是搜索树,这次搜索有方向了!| LeetCode700.二叉搜索树中的搜索](https://www.bilibili.com/video/BV1wG411g7sF),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
之前我们讲了都是普通二叉树,那么接下来看看二叉搜索树。 之前我们讲了都是普通二叉树,那么接下来看看二叉搜索树。
@ -33,7 +38,7 @@
本题,其实就是在二叉搜索树中搜索一个节点。那么我们来看看应该如何遍历。 本题,其实就是在二叉搜索树中搜索一个节点。那么我们来看看应该如何遍历。
## 递归法 ### 递归法
1. 确定递归函数的参数和返回值 1. 确定递归函数的参数和返回值
@ -106,7 +111,7 @@ public:
``` ```
## 迭代法 ### 迭代法
一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。 一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。
@ -140,7 +145,7 @@ public:
第一次看到了如此简单的迭代法,是不是感动的痛哭流涕,哭一会~ 第一次看到了如此简单的迭代法,是不是感动的痛哭流涕,哭一会~
# 总结 ## 总结
本篇我们介绍了二叉搜索树的遍历方式,因为二叉搜索树的有序性,遍历的时候要比普通二叉树简单很多。 本篇我们介绍了二叉搜索树的遍历方式,因为二叉搜索树的有序性,遍历的时候要比普通二叉树简单很多。
@ -153,9 +158,9 @@ public:
# 其他语言版本 ## 其他语言版本
## Java ### Java
```Java ```Java
class Solution { class Solution {
@ -222,7 +227,7 @@ class Solution {
} }
``` ```
## Python ### Python
递归法: 递归法:
@ -257,7 +262,7 @@ class Solution:
``` ```
## Go ### Go
递归法: 递归法:
@ -292,7 +297,7 @@ func searchBST(root *TreeNode, val int) *TreeNode {
} }
``` ```
## JavaScript ### JavaScript
递归: 递归:
@ -350,7 +355,7 @@ var searchBST = function (root, val) {
}; };
``` ```
## TypeScript ### TypeScript
> 递归法 > 递归法
@ -380,7 +385,7 @@ function searchBST(root: TreeNode | null, val: number): TreeNode | null {
}; };
``` ```
## Scala ### Scala
递归: 递归:
```scala ```scala