mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-08 00:43:04 +08:00
Update
This commit is contained in:
@ -27,7 +27,7 @@
|
||||
|
||||

|
||||
|
||||
## 视频讲解
|
||||
# 视频讲解
|
||||
|
||||
**《代码随想录》算法视频公开课:[坑很多!来看看你掉过几次坑 | LeetCode:106.从中序与后序遍历序列构造二叉树](https://www.bilibili.com/video/BV1vW4y1i7dn),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
|
||||
|
||||
|
@ -68,7 +68,7 @@ fast和slow各自再走一步, fast和slow就相遇了
|
||||
环形入口节点到 fast指针与slow指针相遇节点 节点数为y。
|
||||
从相遇节点 再到环形入口节点节点数为 z。 如图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
那么相遇时:
|
||||
slow指针走过的节点数为: `x + y`,
|
||||
|
@ -98,7 +98,7 @@ public:
|
||||
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((long)num2 * (long)num1); //力扣改了后台测试数据
|
||||
if (tokens[i] == "/") st.push(num2 / num1);
|
||||
} else {
|
||||
st.push(stoi(tokens[i]));
|
||||
|
@ -38,24 +38,32 @@
|
||||
# 思路
|
||||
|
||||
|
||||
|
||||
做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有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的左子树,和右子树中。
|
||||
|
||||

|
||||
|
||||
此时节点5是不是最近公共祖先? 如果 从节点5继续向左遍历,那么将错过成为q的祖先, 如果从节点5继续向右遍历则错过成为p的祖先。
|
||||
|
||||
所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间中,那么cur就是 p和q的最近公共祖先。
|
||||
|
||||
理解这一点,本题就很好解了。
|
||||
|
||||
和[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)不同,普通二叉树求最近公共祖先需要使用回溯,从底向上来查找,二叉搜索树就不用了,因为搜索树有序(相当于自带方向),那么只要从上向下遍历就可以了。
|
||||
|
||||
那么我们可以采用前序遍历(其实这里没有中节点的处理逻辑,遍历顺序无所谓了)。
|
||||
而递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。
|
||||
|
||||
如图所示:p为节点3,q为节点5
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!
|
||||
|
||||
|
@ -41,23 +41,29 @@
|
||||
|
||||
回溯啊,二叉树回溯的过程就是从低到上。
|
||||
|
||||
后序遍历就是天然的回溯过程,最先处理的一定是叶子节点。
|
||||
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
|
||||
|
||||
接下来就看如何判断一个节点是节点q和节点p的公共公共祖先呢。
|
||||
|
||||
**首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。**
|
||||
**首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。** 即情况一:
|
||||
|
||||
**但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。**
|
||||

|
||||
|
||||
使用后序遍历,回溯的过程,就是从低向上遍历节点,一旦发现满足第一种情况的节点,就是最近公共节点了。
|
||||
判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。
|
||||
|
||||
**但是如果p或者q本身就是最近公共祖先呢**?
|
||||
那么有录友可能疑惑,会不会左子树 遇到q 返回,右子树也遇到q返回,这样并没有找到 q 和p的最近祖先。
|
||||
|
||||
其实只需要找到一个节点是p或者q的时候,直接返回当前节点,无需继续递归子树。
|
||||
这么想的录友,要审题了,题目强调:**二叉树节点数值是不重复的,而且一定存在 q 和 p**。
|
||||
|
||||
如果接下来的遍历中找到了后继节点满足第一种情况则修改返回值为后继节点,否则,继续返回已找到的节点即可。
|
||||
**但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。** 情况二:
|
||||
|
||||
为什么满足第一种情况的节点一定是p或q的后继节点呢?大家可以仔细思考一下。
|
||||

|
||||
|
||||
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
|
||||
|
||||
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本省就是 公共祖先的情况。
|
||||
|
||||
这一点是很多录友容易忽略的,在下面的代码讲解中,可以在去体会。
|
||||
|
||||
递归三部曲:
|
||||
|
||||
@ -69,20 +75,24 @@
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
```CPP
|
||||
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;
|
||||
```
|
||||
|
||||
|
||||
|
||||
* 确定单层递归逻辑
|
||||
|
||||
值得注意的是 本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历树的所有节点。
|
||||
@ -93,7 +103,7 @@ if (root == q || root == p || root == NULL) return root;
|
||||
|
||||
搜索一条边的写法:
|
||||
|
||||
```
|
||||
```CPP
|
||||
if (递归函数(root->left)) return ;
|
||||
|
||||
if (递归函数(root->right)) return ;
|
||||
@ -101,10 +111,10 @@ if (递归函数(root->right)) return ;
|
||||
|
||||
搜索整个树写法:
|
||||
|
||||
```
|
||||
left = 递归函数(root->left);
|
||||
right = 递归函数(root->right);
|
||||
left与right的逻辑处理;
|
||||
```CPP
|
||||
left = 递归函数(root->left); // 左
|
||||
right = 递归函数(root->right); // 右
|
||||
left与right的逻辑处理; // 中
|
||||
```
|
||||
|
||||
看出区别了没?
|
||||
@ -123,10 +133,10 @@ left与right的逻辑处理;
|
||||
|
||||
因为在如下代码的后序遍历中,如果想利用left和right做逻辑处理, 不能立刻返回,而是要等left与right逻辑处理完之后才能返回。
|
||||
|
||||
```
|
||||
left = 递归函数(root->left);
|
||||
right = 递归函数(root->right);
|
||||
left与right的逻辑处理;
|
||||
```CPP
|
||||
left = 递归函数(root->left); // 左
|
||||
right = 递归函数(root->right); // 右
|
||||
left与right的逻辑处理; // 中
|
||||
```
|
||||
|
||||
所以此时大家要知道我们要遍历整棵树。知道这一点,对本题就有一定深度的理解了。
|
||||
@ -134,7 +144,7 @@ left与right的逻辑处理;
|
||||
|
||||
那么先用left和right接住左子树和右子树的返回值,代码如下:
|
||||
|
||||
```
|
||||
```CPP
|
||||
TreeNode* left = lowestCommonAncestor(root->left, p, q);
|
||||
TreeNode* right = lowestCommonAncestor(root->right, p, q);
|
||||
|
||||
|
@ -19,13 +19,18 @@
|
||||
|
||||
注意: 合并必须从两个树的根节点开始。
|
||||
|
||||
# 思路
|
||||
# 视频讲解
|
||||
|
||||
**《代码随想录》算法视频公开课:[一起操作两个二叉树?有点懵!| LeetCode:617.合并二叉树](https://www.bilibili.com/video/BV1m14y1Y7JK),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
|
||||
|
||||
|
||||
## 思路
|
||||
|
||||
相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢?
|
||||
|
||||
其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。
|
||||
|
||||
## 递归
|
||||
### 递归
|
||||
|
||||
二叉树使用递归,就要想使用前中后哪种遍历方式?
|
||||
|
||||
@ -207,7 +212,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
# 拓展
|
||||
## 拓展
|
||||
|
||||
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。
|
||||
|
||||
@ -239,7 +244,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
# 总结
|
||||
## 总结
|
||||
|
||||
合并二叉树,也是二叉树操作的经典题目,如果没有接触过的话,其实并不简单,因为我们习惯了操作一个二叉树,一起操作两个二叉树,还会有点懵懵的。
|
||||
|
||||
@ -250,10 +255,10 @@ public:
|
||||
最后拓展中,我给了一个操作指针的野路子,大家随便看看就行了,如果学习C++的话,可以在去研究研究。
|
||||
|
||||
|
||||
# 其他语言版本
|
||||
## 其他语言版本
|
||||
|
||||
|
||||
## Java
|
||||
### Java
|
||||
|
||||
```Java
|
||||
class Solution {
|
||||
@ -346,7 +351,7 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
### Python
|
||||
|
||||
**递归法 - 前序遍历**
|
||||
```python
|
||||
@ -409,7 +414,7 @@ class Solution:
|
||||
return root1
|
||||
```
|
||||
|
||||
## 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 {
|
||||
if (root1 === null) return root2;
|
||||
if (root2 === null) return root1;
|
||||
@ -631,7 +636,7 @@ function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode |
|
||||
};
|
||||
```
|
||||
|
||||
## Scala
|
||||
### Scala
|
||||
|
||||
递归:
|
||||
```scala
|
||||
|
@ -25,7 +25,12 @@
|
||||
|
||||
给定的数组的大小在 [1, 1000] 之间。
|
||||
|
||||
# 思路
|
||||
## 视频讲解
|
||||
|
||||
**《代码随想录》算法视频公开课:[又是构造二叉树,又有很多坑!| LeetCode:654.最大二叉树](https://www.bilibili.com/video/BV1MG411G7ox),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
|
||||
|
||||
|
||||
## 思路
|
||||
|
||||
最大二叉树的构建过程如下:
|
||||
|
||||
@ -175,7 +180,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
# 拓展
|
||||
## 拓展
|
||||
|
||||
可以发现上面的代码看上去简洁一些,**主要是因为第二版其实是允许空节点进入递归,所以不用在递归的时候加判断节点是否为空**
|
||||
|
||||
@ -207,7 +212,7 @@ root->right = traversal(nums, maxValueIndex + 1, right);
|
||||
|
||||
第二版相应的终止条件,是遇到空节点,也就是数组区间为0,就终止了。
|
||||
|
||||
# 总结
|
||||
## 总结
|
||||
|
||||
|
||||
这道题目其实和 [二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 是一个思路,比[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html) 还简单一些。
|
||||
@ -218,10 +223,10 @@ root->right = traversal(nums, maxValueIndex + 1, right);
|
||||
|
||||
其实就是不同代码风格的实现,**一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。**
|
||||
|
||||
# 其他语言版本
|
||||
## 其他语言版本
|
||||
|
||||
|
||||
## Java
|
||||
### Java
|
||||
|
||||
```Java
|
||||
class Solution {
|
||||
@ -253,7 +258,7 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
### Python
|
||||
|
||||
```python
|
||||
class Solution:
|
||||
@ -300,7 +305,7 @@ class Solution:
|
||||
return root
|
||||
```
|
||||
|
||||
## Go
|
||||
### Go
|
||||
|
||||
|
||||
```go
|
||||
@ -334,7 +339,7 @@ func findMax(nums []int) (index int){
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
struct TreeNode* traversal(int* nums, int left, int right) {
|
||||
@ -450,7 +455,7 @@ struct TreeNode* constructMaximumBinaryTree(int* nums, int numsSize){
|
||||
}
|
||||
```
|
||||
|
||||
## Swift
|
||||
### Swift
|
||||
```swift
|
||||
func constructMaximumBinaryTree(_ nums: inout [Int]) -> TreeNode? {
|
||||
return traversal(&nums, 0, nums.count)
|
||||
@ -476,7 +481,7 @@ func traversal(_ nums: inout [Int], _ left: Int, _ right: Int) -> TreeNode? {
|
||||
}
|
||||
```
|
||||
|
||||
## Scala
|
||||
### Scala
|
||||
|
||||
```scala
|
||||
object Solution {
|
||||
|
@ -17,7 +17,12 @@
|
||||
|
||||
在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。
|
||||
|
||||
# 思路
|
||||
# 视频讲解
|
||||
|
||||
**《代码随想录》算法视频公开课:[不愧是搜索树,这次搜索有方向了!| LeetCode:700.二叉搜索树中的搜索](https://www.bilibili.com/video/BV1wG411g7sF),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
|
||||
|
||||
|
||||
## 思路
|
||||
|
||||
之前我们讲了都是普通二叉树,那么接下来看看二叉搜索树。
|
||||
|
||||
@ -33,7 +38,7 @@
|
||||
|
||||
本题,其实就是在二叉搜索树中搜索一个节点。那么我们来看看应该如何遍历。
|
||||
|
||||
## 递归法
|
||||
### 递归法
|
||||
|
||||
1. 确定递归函数的参数和返回值
|
||||
|
||||
@ -106,7 +111,7 @@ public:
|
||||
```
|
||||
|
||||
|
||||
## 迭代法
|
||||
### 迭代法
|
||||
|
||||
一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。
|
||||
|
||||
@ -140,7 +145,7 @@ public:
|
||||
|
||||
第一次看到了如此简单的迭代法,是不是感动的痛哭流涕,哭一会~
|
||||
|
||||
# 总结
|
||||
## 总结
|
||||
|
||||
本篇我们介绍了二叉搜索树的遍历方式,因为二叉搜索树的有序性,遍历的时候要比普通二叉树简单很多。
|
||||
|
||||
@ -153,9 +158,9 @@ public:
|
||||
|
||||
|
||||
|
||||
# 其他语言版本
|
||||
## 其他语言版本
|
||||
|
||||
## Java
|
||||
### Java
|
||||
|
||||
```Java
|
||||
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
|
||||
|
Reference in New Issue
Block a user