Merge pull request #1799 from juguagua/leetcode-modify-the-code-of-the-BinaryTree

完善更新二叉树部分:从 “最大二叉树” 到 “删除二叉搜索树中的节点”
This commit is contained in:
程序员Carl
2022-12-06 10:38:24 +08:00
committed by GitHub
13 changed files with 166 additions and 258 deletions

View File

@ -437,8 +437,6 @@ class Solution:
## Go
```Go
import "math"
func isValidBST(root *TreeNode) bool {
// 二叉搜索树也可以是空树
if root == nil {

View File

@ -35,7 +35,7 @@
因为只要给我们一个有序数组,如果强调平衡,都可以以线性结构来构造二叉搜索树。
例如 有序数组[-10-3059] 可以就可以构造成这样的二叉搜索树,如图。
例如 有序数组[-10-3059] 就可以构造成这样的二叉搜索树,如图。
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220930173553.png)
@ -147,7 +147,7 @@ public:
};
```
**注意在调用traversal的时候为什么传入的left和right为什么是0和nums.size() - 1因为定义的区间为左闭右闭**
**注意在调用traversal的时候传入的left和right为什么是0和nums.size() - 1因为定义的区间为左闭右闭**
## 迭代法
@ -354,10 +354,15 @@ class Solution:
```go
func sortedArrayToBST(nums []int) *TreeNode {
if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回
root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点从中间构造节点
root.Left=sortedArrayToBST(nums[:len(nums)/2])//数组的左边为左子树
root.Right=sortedArrayToBST(nums[len(nums)/2+1:])//数字的右边为右子树
if len(nums) == 0 { //终止条件,最后数组为空则可以返回
return nil
}
idx := len(nums)/2
root := &TreeNode{Val: nums[idx]}
root.Left = sortedArrayToBST(nums[:idx])
root.Right = sortedArrayToBST(nums[idx+1:])
return root
}
```

View File

@ -303,14 +303,22 @@ class Solution:
递归法:
```go
//利用BSL的性质前序遍历有序
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root==nil{return nil}
if root.Val>p.Val&&root.Val>q.Val{//当前节点的值大于给定的值,则说明满足条件的在左边
return lowestCommonAncestor(root.Left,p,q)
}else if root.Val<p.Val&&root.Val<q.Val{//当前节点的值小于各点的值,则说明满足条件的在右边
return lowestCommonAncestor(root.Right,p,q)
}else {return root}//当前节点的值在给定值的中间(或者等于),即为最深的祖先
if root == nil {
return nil
}
for {
if root.Val > p.Val && root.Val > q.Val {
root = root.Left
}
if root.Val < p.Val && root.Val < q.Val {
root = root.Right
}
if (root.Val - p.Val) * (root.Val - q.Val) <= 0 {
return root
}
}
return root
}
```

View File

@ -48,7 +48,7 @@
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
接下来就看如何判断一个节点是节点q和节点p的公共公共祖先呢。
接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。
**首先最容易想到的一个情况如果找到一个节点发现左子树出现结点p右子树出现节点q或者 左子树出现结点q右子树出现节点p那么该节点就是节点p和q的最近公共祖先。** 即情况一:
@ -66,9 +66,9 @@
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本就是 公共祖先的情况。
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本就是 公共祖先的情况。
这一点是很多录友容易忽略的,在下面的代码讲解中,可以去体会。
这一点是很多录友容易忽略的,在下面的代码讲解中,可以去体会。
递归三部曲:
@ -86,9 +86,9 @@ TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
* 确定终止条件
遇到空的话,然后然后空,因为树都是空了,所以返回空。
遇到空的话,因为树都是空了,所以返回空。
那么我们来说一说,如果 root == q或者 root == p说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点处理逻辑,下面讲解。
那么我们来说一说,如果 root == q或者 root == p说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点处理逻辑,下面讲解。
代码如下:
@ -167,7 +167,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q);
图中节点10的左子树返回null右子树返回目标值7那么此时节点10的处理逻辑就是把右子树的返回值最近公共祖先7返回上去
这里也很重要,可能刷过这道题目的同学,都不清楚结果究竟是如何从底层一层一层传到头结点的。
这里也很重要,可能刷过这道题目的同学,都不清楚结果究竟是如何从底层一层一层传到头结点的。
那么如果left和right都为空则返回left或者right都是可以的也就是返回空。
@ -231,7 +231,7 @@ public:
**那么我给大家归纳如下三点**
1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从向上的遍历方式。
1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从向上的遍历方式。
2. 在回溯的过程中必然要遍历整棵二叉树即使已经找到结果了依然要把其他节点遍历完因为要使用递归函数的返回值也就是代码中的left和right做逻辑判断。

View File

@ -25,7 +25,7 @@
# 思路
搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心准备。
搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心准备。
## 递归
@ -33,7 +33,7 @@
* 确定递归函数参数以及返回值
递归函数的返回值,在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。
递归函数的返回值,在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。
代码如下:
@ -66,7 +66,7 @@ if (root == nullptr) return root;
![450.删除二叉搜索树中的节点](https://tva1.sinaimg.cn/large/008eGmZEly1gnbj3k596mg30dq0aigyz.gif)
动画中二叉搜索树中删除元素7 那么删除节点元素7的左孩子就是5删除节点元素7的右子树的最左面节点是元素8。
动画中二叉搜索树中删除元素7 那么删除节点元素7的左孩子就是5删除节点元素7的右子树的最左面节点是元素8。
将删除节点元素7的左孩子放到删除节点元素7的右子树的最左面节点元素8的左孩子上就是把5为根节点的子树移到了8的左孩子的位置。
@ -251,7 +251,7 @@ public:
**这里最关键的逻辑就是第五种情况(删除一个左右孩子都不为空的节点),这种情况一定要想清楚**
而且就算想清楚了,对应的代码也未必可以写出来,所以**这道题目考察思维逻辑,也考察代码能力**。
而且就算想清楚了,对应的代码也未必可以写出来,所以**这道题目考察思维逻辑,也考察代码能力**。
递归中我给出了两种写法,推荐大家学会第一种(利用搜索树的特性)就可以了,第二种递归写法其实是比较绕的。

View File

@ -54,7 +54,7 @@
1. 这个树都遍历了用map统计频率
至于用前中后序种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
至于用前中后序种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
这里采用前序遍历,代码如下:
@ -354,7 +354,7 @@ public:
```java
class Solution {
public int[] findMode(FindModeInBinarySearchTree.TreeNode root) {
public int[] findMode(TreeNode root) {
Map<Integer, Integer> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
if (root == null) return list.stream().mapToInt(Integer::intValue).toArray();
@ -375,7 +375,7 @@ class Solution {
return list.stream().mapToInt(Integer::intValue).toArray();
}
void searchBST(FindModeInBinarySearchTree.TreeNode curr, Map<Integer, Integer> map) {
void searchBST(TreeNode curr, Map<Integer, Integer> map) {
if (curr == null) return;
map.put(curr.val, map.getOrDefault(curr.val, 0) + 1);
searchBST(curr.left, map);
@ -556,46 +556,7 @@ class Solution:
```
## Go
暴力法非BSL
```go
func findMode(root *TreeNode) []int {
var history map[int]int
var maxValue int
var maxIndex int
var result []int
history=make(map[int]int)
traversal(root,history)
for k,value:=range history{
if value>maxValue{
maxValue=value
maxIndex=k
}
}
for k,value:=range history{
if value==history[maxIndex]{
result=append(result,k)
}
}
return result
}
func traversal(root *TreeNode,history map[int]int){
if root.Left!=nil{
traversal(root.Left,history)
}
if value,ok:=history[root.Val];ok{
history[root.Val]=value+1
}else{
history[root.Val]=1
}
if root.Right!=nil{
traversal(root.Right,history)
}
}
```
计数法,不使用额外空间,利用二叉树性质,中序遍历
```go
func findMode(root *TreeNode) []int {
res := make([]int, 0)

View File

@ -248,28 +248,6 @@ class Solution:
## Go
中序遍历,然后计算最小差值
```go
func getMinimumDifference(root *TreeNode) int {
var res []int
findMIn(root,&res)
min:=1000000//一个比较大的值
for i:=1;i<len(res);i++{
tempValue:=res[i]-res[i-1]
if tempValue<min{
min=tempValue
}
}
return min
}
//中序遍历
func findMIn(root *TreeNode,res *[]int){
if root==nil{return}
findMIn(root.Left,res)
*res=append(*res,root.Val)
findMIn(root.Right,res)
}
```
```go
// 中序遍历的同时计算最小值
func getMinimumDifference(root *TreeNode) int {

View File

@ -45,11 +45,11 @@
# 思路
一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后遍历其他节点累加?怎么一想这么麻烦呢。
一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后遍历其他节点累加?怎么一想这么麻烦呢。
然后再发现这是一棵二叉搜索树,二叉搜索树啊,这是有序的啊。
那么有序的元素如求累加呢?
那么有序的元素如求累加呢?
**其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。**
@ -233,22 +233,22 @@ class Solution:
## Go
弄一个sum暂存其和值
```go
//右中左
func bstToGst(root *TreeNode) *TreeNode {
var sum int
RightMLeft(root,&sum)
var pre int
func convertBST(root *TreeNode) *TreeNode {
pre = 0
traversal(root)
return root
}
func RightMLeft(root *TreeNode,sum *int) *TreeNode {
if root==nil{return nil}//终止条件,遇到空节点就返回
RightMLeft(root.Right,sum)//先遍历右边
temp:=*sum//暂存总和值
*sum+=root.Val//将总和值变更
root.Val+=temp//更新节点值
RightMLeft(root.Left,sum)//遍历左节点
return root
func traversal(cur *TreeNode) {
if cur == nil {
return
}
traversal(cur.Right)
cur.Val += pre
pre = cur.Val
traversal(cur.Left)
}
```

View File

@ -46,7 +46,7 @@
1. **确定递归函数的参数和返回值:**
首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
代码如下:
@ -56,7 +56,7 @@ TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
2. **确定终止条件:**
因为是传入了两个树那么就有两个树遍历的节点t1 和 t2如果t1 == NULL 了,两个树合并就应该是 t2 了如果t2也为NULL也无所谓合并之后就是NULL
因为是传入了两个树那么就有两个树遍历的节点t1 和 t2如果t1 == NULL 了,两个树合并就应该是 t2 了如果t2也为NULL也无所谓合并之后就是NULL
反过来如果t2 == NULL那么两个数合并就是t1如果t1也为NULL也无所谓合并之后就是NULL
@ -70,7 +70,7 @@ if (t2 == NULL) return t1; // 如果t2为空合并之后就应该是t1
3. **确定单层递归的逻辑:**
单层递归的逻辑就比较好了,这里我们重复利用一下t1这个树t1就是合并之后树的根节点就是修改了原来树的结构
单层递归的逻辑就比较好这里我们重复利用一下t1这个树t1就是合并之后树的根节点就是修改了原来树的结构
那么单层递归中,就要把两棵树的元素加到一起。
```
@ -144,7 +144,7 @@ public:
**但是前序遍历是最好理解的我建议大家用前序遍历来做就OK。**
如上的方法修改了t1的结构当然也可以不修改t1和t2的结构重新定一个树。
如上的方法修改了t1的结构当然也可以不修改t1和t2的结构重新定一个树。
不修改输入树的结构,前序遍历,代码如下:
@ -214,7 +214,7 @@ public:
## 拓展
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑了。
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑了。
如下代码中,想要更改二叉树的值,应该传入指向指针的指针。
@ -252,7 +252,7 @@ public:
迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。
最后拓展中我给了一个操作指针的野路子大家随便看看就行了如果学习C++的话,可以去研究研究。
最后拓展中我给了一个操作指针的野路子大家随便看看就行了如果学习C++的话,可以去研究研究。
## 其他语言版本
@ -417,43 +417,7 @@ class Solution:
### Go
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
//前序遍历递归遍历跟105 106差不多的思路
func mergeTrees(t1 *TreeNode, t2 *TreeNode) *TreeNode {
var value int
var nullNode *TreeNode//空node便于遍历
nullNode=&TreeNode{
Val:0,
Left:nil,
Right:nil}
switch {
case t1==nil&&t2==nil: return nil//终止条件
default : //如果其中一个节点为空则将该节点置为nullNode方便下次遍历
if t1==nil{
value=t2.Val
t1=nullNode
}else if t2==nil{
value=t1.Val
t2=nullNode
}else {
value=t1.Val+t2.Val
}
}
root:=&TreeNode{//构造新的二叉树
Val: value,
Left: mergeTrees(t1.Left,t2.Left),
Right: mergeTrees(t1.Right,t2.Right)}
return root
}
// 前序遍历简洁版
// 前序遍历
func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
if root1 == nil {
return root2

View File

@ -40,7 +40,7 @@
* 确定递归函数的参数和返回值
参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
代码如下:
@ -309,19 +309,13 @@ class Solution:
```go
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func constructMaximumBinaryTree(nums []int) *TreeNode {
if len(nums)<1{return nil}
//首选找到最大值
if len(nums) == 0 {
return nil
}
// 找到最大值
index := findMax(nums)
//其次构造二叉树
// 构造二叉树
root := &TreeNode {
Val: nums[index],
Left: constructMaximumBinaryTree(nums[:index]), //左半边
@ -330,8 +324,8 @@ func constructMaximumBinaryTree(nums []int) *TreeNode {
return root
}
func findMax(nums []int) (index int) {
for i:=0;i<len(nums);i++{
if nums[i]>nums[index]{
for i, v := range nums {
if nums[index] < v {
index = i
}
}

View File

@ -59,7 +59,7 @@ public:
![669.修剪二叉搜索树1](https://img-blog.csdnimg.cn/20210204155327203.png)
理解了最关键部分了我们递归三部曲:
理解了最关键部分了我们递归三部曲:
* 确定递归函数的参数以及返回值
@ -179,7 +179,7 @@ public:
};
```
只看代码,其实不太好理解节点是符合移除的,这一块大家可以自己模拟模拟!
只看代码,其实不太好理解节点是如何移除的,这一块大家可以自己模拟模拟!
## 迭代法

View File

@ -24,7 +24,7 @@
## 思路
之前我们讲都是普通二叉树,那么接下来看看二叉搜索树。
之前我们讲都是普通二叉树,那么接下来看看二叉搜索树。
在[关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)中,我们已经讲过了二叉搜索树。
@ -290,7 +290,7 @@ func searchBST(root *TreeNode, val int) *TreeNode {
} else if root.Val < val {
root = root.Right
} else {
break
return root
}
}
return nil

View File

@ -24,7 +24,7 @@
# 思路
其实这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。
这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。
其实**可以不考虑题目中提示所说的改变树的结构的插入方式。**
@ -157,7 +157,7 @@ public:
我之所以举这个例子,是想说明通过递归函数的返回值完成父子节点的赋值是可以带来便利的。
**网上千一律的代码,可能会误导大家认为通过递归函数返回节点 这样的写法是天经地义,其实这里是有优化的!**
**网上千一律的代码,可能会误导大家认为通过递归函数返回节点 这样的写法是天经地义,其实这里是有优化的!**
## 迭代
@ -197,7 +197,7 @@ public:
首先在二叉搜索树中的插入操作,大家不用恐惧其重构搜索树,其实根本不用重构。
然后在递归中,我们重点讲了如通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。
然后在递归中,我们重点讲了如通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。
最后依然给出了迭代的方法,迭代的方法就需要记录当前遍历节点的父节点了,这个和没有返回值的递归函数实现的代码逻辑是一样的。