669.修建二叉树

文章:31. 修剪二叉搜索树

题目:669. 修剪二叉搜索树

视频:你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树_哔哩哔哩_bilibili

【常见误区】

不能直接return删除当前节点后的左右子树,有可能左右子树里面也有需要删除的节点。因此我们需要return的是当前函数(递归)

  • Java实现
1
2
3
4
5
6
7
8
9
10
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null)return root;
if(root.val > high)return trimBST(root.left,low,high);
if(root.val < low)return trimBST(root.right,low,high);
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
return root;
}
}
  • Go实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func trimBST(root *TreeNode, low int, high int) *TreeNode {
if root == nil{
return nil
}
if root.Val < low{
left := trimBST(root.Right,low,high)
return left
}
if root.Val > high{
right := trimBST(root.Left,low,high)
return right
}
root.Left = trimBST(root.Left,low,high)
root.Right = trimBST(root.Right,low,high)
return root
}

108.将有序数组转换为二叉搜索树

文章:32. 将有序数组转换为二叉搜索树

题目:108. 将有序数组转换为二叉搜索树

【思路】

二编:一个普通的二分?,但是需要确定好边界,光传递切片还是不够二分的


学习到了:

​ 不难写出int mid = (left + right) / 2;这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法 (opens new window)中尤其需要注意!

所以可以这么写:int mid = left + ((right - left) / 2);

遵循左闭右开原则!

  • Java实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode root = treeAdd(nums,0,nums.length-1);
return root;
}
public TreeNode treeAdd(int[] nums,int l , int r){
if (l > r){
return null;
}
int mid = l +((r - l)/2);
TreeNode root = new TreeNode(nums[mid]);
root.left = treeAdd(nums,l,mid-1);
root.right = treeAdd(nums,mid+1,r);
return root;
}
}
  • Go实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func sortedArrayToBST(nums []int) *TreeNode {
//二分
return traversal(nums,0,len(nums)-1)
}
func traversal(nums []int,l,r int) *TreeNode{
if l > r {
return nil
}
//l+r = 2*l - (l-r)
mid := l - (l-r)/2
node := &TreeNode{Val: nums[mid]}
node.Left = traversal(nums,l,mid-1)
node.Right = traversal(nums,mid+1,r)
return node
}

538.把二叉搜索树转换为累加树

文章:33. 把二叉搜索树转换为累加树

题目:538. 把二叉搜索树转换为累加树

【思路】

从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了

递归

  • 递归函数参数以及返回值

要遍历整棵树,因此不需要递归函数的返回值做什么操作了。同时需要一个全局变量pre、来保存cur节点的前一个结点的数值

  • 确定终止条件

遇到空就停止

  • 确定单层递归逻辑

注意要右中左来遍历二叉树,中节点的处理逻辑就是让cur的数值加上前一个结点的数值。

  • Java实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
int pre =0;
public TreeNode convertBST(TreeNode root) {
traversal(root);
return root;
}
public void traversal(TreeNode cur){
if(cur == null)return;
traversal(cur.right);
cur.val += pre;
pre = cur.val;
traversal(cur.left);
}
}
  • Go实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func convertBST(root *TreeNode) *TreeNode {
pre :=0

var traversal func(cur *TreeNode)
traversal = func(cur *TreeNode){
//右中左
if cur == nil{
return
}
traversal(cur.Right)
cur.Val += pre
//忘记更新pre节点了
pre = cur.Val
traversal(cur.Left)
}
traversal(root)
return root
}

还是要多多理解递归哇!


总结篇

【先说个人】

​ 本章节学习了二叉树包括种类、遍历顺序(前中后序遍历)、各种不同的操作、递归法&&迭代法等等的知识,但是由于各方面的原因绝大部分的题目都没有进行迭代法解法的学习,是这次一刷的小遗憾之一。

​ 同时,我写本章节期间也有许多理解的不够透彻的题目包括但不限于:

​ 因此,我希望之后的时间里面:

  • 找空闲时间来复盘以上提到的题目
  • 继续坚持算法的打卡
  • 之后要二刷将迭代法刷一遍

​ 但是同时我也比较担心,12月有比较多的诸如期末考等等优先级更高更棘手的事情需要解决,有点担心不能做到准时打卡。但,既来之则安之吧。

【二叉树总结】

参考文章:34. 二叉树总结篇

首先,递归法最常使用的肯定是递归了。

【递归三部曲】

  • 确定参数以及返回值
  • 确定终止条件
  • 确定单层逻辑

二叉树的理论基础


二叉树的遍历方式

  • 深度优先遍历:前中后序遍历
  • 广度优先遍历:层序遍历

二叉树的属性:

  • 二叉树:是否对称(opens new window)
    • 递归:后序,比较的是根节点的左子树与右子树是不是相互翻转
    • 迭代:使用队列/栈将两个节点顺序放入容器中进行比较
  • 二叉树:求最大深度(opens new window)
    • 递归:后序,求根节点最大高度就是最大深度,通过递归函数的返回值做计算树的高度
    • 迭代:层序遍历
  • 二叉树:求最小深度(opens new window)
    • 递归:后序,求根节点最小高度就是最小深度,注意最小深度的定义
    • 迭代:层序遍历
  • 二叉树:求有多少个节点(opens new window)
    • 递归:后序,通过递归函数的返回值计算节点数量
    • 迭代:层序遍历
  • 二叉树:是否平衡(opens new window)
    • 递归:后序,注意后序求高度和前序求深度,递归过程判断高度差
    • 迭代:效率很低,不推荐
  • 二叉树:找所有路径(opens new window)
    • 递归:前序,方便让父节点指向子节点,涉及回溯处理根节点到叶子的所有路径
    • 迭代:一个栈模拟递归,一个栈来存放对应的遍历路径
  • 二叉树:递归中如何隐藏着回溯(opens new window)
  • 二叉树:求左叶子之和(opens new window)
    • 递归:后序,必须三层约束条件,才能判断是否是左叶子。
    • 迭代:直接模拟后序遍历
  • 二叉树:求左下角的值(opens new window)
    • 递归:顺序无所谓,优先左孩子搜索,同时找深度最大的叶子节点。
    • 迭代:层序遍历找最后一行最左边
  • 二叉树:求路径总和(opens new window)
    • 递归:顺序无所谓,递归函数返回值为bool类型是为了搜索一条边,没有返回值是搜索整棵树。
    • 迭代:栈里元素不仅要记录节点指针,还要记录从头结点到该节点的路径数值总和

#二叉树的修改与构造

  • 翻转二叉树(opens new window)
    • 递归:前序,交换左右孩子
    • 迭代:直接模拟前序遍历
  • 构造二叉树(opens new window)
    • 递归:前序,重点在于找分割点,分左右区间构造
    • 迭代:比较复杂,意义不大
  • 构造最大的二叉树(opens new window)
    • 递归:前序,分割点为数组最大值,分左右区间构造
    • 迭代:比较复杂,意义不大
  • 合并两个二叉树(opens new window)
    • 递归:前序,同时操作两个树的节点,注意合并的规则
    • 迭代:使用队列,类似层序遍历

#求二叉搜索树的属性

#二叉树公共祖先问题

  • 二叉树的公共祖先问题(opens new window)
    • 递归:后序,回溯,找到左子树出现目标值,右子树节点目标值的节点。
    • 迭代:不适合模拟回溯
  • 二叉搜索树的公共祖先问题(opens new window)
    • 递归:顺序无所谓,如果节点的数值在目标区间就是最近公共祖先
    • 迭代:按序遍历

#二叉搜索树的修改与构造

  • 二叉搜索树中的插入操作(opens new window)
    • 递归:顺序无所谓,通过递归函数返回值添加节点
    • 迭代:按序遍历,需要记录插入父节点,这样才能做插入操作
  • 二叉搜索树中的删除操作(opens new window)
    • 递归:前序,想清楚删除非叶子节点的情况
    • 迭代:有序遍历,较复杂
  • 修剪二叉搜索树(opens new window)
    • 递归:前序,通过递归函数返回值删除节点
    • 迭代:有序遍历,较复杂
  • 构造二叉搜索树(opens new window)
    • 递归:前序,数组中间节点分割
    • 迭代:较复杂,通过三个队列来模拟

#阶段总结

大家以上题目都做过了,也一定要看如下阶段小结。

每周小结都会对大家的疑问做统一解答,并且对每周的内容进行拓展和补充,所以一定要看,将细碎知识点一网打尽!

#最后总结

在二叉树题目选择什么遍历顺序是不少同学头疼的事情,我们做了这么多二叉树的题目了,Carl给大家大体分分类

  • 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
  • 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
  • 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。

注意在普通二叉树的属性中,我用的是一般为后序,例如单纯求深度就用前序,二叉树:找所有路径 (opens new window)也用了前序,这是为了方便让父节点指向子节点。

所以求普通二叉树的属性还是要具体问题具体分析。