代码随想录算法训练营 DAY 23 | 450.删除二叉搜索树中的节点 669.修剪二叉搜索树 538.把二叉搜索树转换为累加树

文章详细讲解了如何修剪二叉搜索树,包括递归删除节点并更新边界的过程,以及二叉搜索树的构造、搜索、插入、删除和公共祖先问题的解决策略,强调了递归和迭代在这些问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

669.修剪二叉搜索树

  • 误区:节点不在范围内不能直接return null,因为它的右子树是可能符合边界的。虽然要删除这个节点,但还要先继续遍历右子树。

递归套递归,删除修剪的同时去递归修剪左/右子树,然后在下面递归左右子树的时候用root.left right接住

  1. 确定递归函数 传入新的左右边界,返回新的根节点
TreeNode trimBST(TreeNode root, int low, int high)
  1. 终止条件
if(root == null) return null;
  1. 单层递归逻辑

如果一个节点在边界左侧,还要往右遍历。它的返回值是这棵右子树修剪完后的根节点。

不能直接return right!因为右子树不一定完全符合边界,要递归处理。

if(root.val < low) {
    right = traversal(root.right,low,high); //返回值要在上一层接住
    return right;  //一会在上一层的左子树接住
}
if(root.val > high) {  //在边界右侧的情况同理
    left = traversal(root.left,low,high);
    return left;   //一会在上一层的右子树接住
}
root.left = traversal(root.left,low,high);
root.right = traversal(root.right,low,high);
return root;
  • 完整代码
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root == null) return null;
        if(root.val < low ) {
            TreeNode right = trimBST(root.right,low,high);
            return right;
        }
        if(root.val > high) {
            TreeNode left = trimBST(root.left,low,high);
            return left;
        }
        root.left = trimBST(root.left,low,high);
        root.right = trimBST(root.right,low,high);
        return root;
    }
}

这一题和上一题都是在递归的推出条件上做的文章, 遍历顺序倒无所谓

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

  • 注意 要构造一棵平衡二叉搜索树

大致思路:每一次都选择中间的节点,将这个数组分为左区间和右区间,递归遍历左区间构造左子树,遍历右区间构造右子树。

长度为偶数怎么办呢?—取左侧或者右侧的都可以

每次进入递归的时候new出来节点赋值,把它return回去 递归的时候用root.left/right接住它

注意区间的定义:保持左闭右闭

  1. 确定递归函数
TreeNode traversal(int[] nums, left, right)
  1. 确定终止条件(什么时候是非法的区间?停止构造)

    当left = right的时候,区间是合法的。

if(left > right) return null;  //此时才是非法区间
  1. 单层递归逻辑
int mid = (left + right)/2;
TreeNode root = new TreeNode(nums[mid]); //构造根节点
//接下来递归构造左右子树
root.left = traversal(nums,left,mid-1);
root.right = traversal(nums,mid+1,right);
return root;
  • 调用递归函数
return traversal(nums,0,nums.length-1);
class Solution {
    public TreeNode traversal(int[] nums, int left, int right) {
        if(left > right) return null;
        int mid = (left + right) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = traversal(nums, left, mid-1); //更新右边界
        root.right = traversal(nums, mid+1, right);
        return root;
    }

    public TreeNode sortedArrayToBST(int[] nums) {
        return traversal(nums,0,nums.length-1);
    }
}

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

  • 思路:如果给一个有序数组,变成累加数组!就是从后往前遍历,逐个累加前面节点的数值。

二叉搜索树的中序遍历(左中右)就是一个有序(升序)数组!

如何倒序遍历呢?右中左!数组的话用双指针实现,二叉树这里也可以用双指针!pre初始化为0

class Solution {
    TreeNode pre = null;
    //右中左 遍历
    public void traversal(TreeNode cur) {
        if(cur == null) return;
        traversal(cur.right);
        if(pre != null) {  //pre不为null才相加
            cur.val = pre.val + cur.val;
        }
        pre = cur;
        traversal(cur.left);
    }

    public TreeNode convertBST(TreeNode root) {
        traversal(root);
        return root;
    }
}

二叉树总结篇

遍历方式

  • 深度优先

    • [二叉树:前中后序递归法:递归三部曲初次亮相
    • [二叉树:前中后序迭代法(一) :通过栈模拟递归
    • [二叉树:前中后序迭代法(二)统一风格]
  • 广度优先

    • [二叉树的层序遍历]:通过队列模拟

求二叉树的属性

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

二叉树的修改与构造

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

求二叉搜索树的属性

  • 二叉搜索树中的搜索
    • 递归:二叉搜索树的递归是有方向的
    • 迭代:因为有方向,所以迭代法很简单
  • 是不是二叉搜索树
    • 递归:中序,相当于变成了判断一个序列是不是递增的
    • 迭代:模拟中序,逻辑相同
  • 求二叉搜索树的最小绝对差
    • 递归:中序,双指针操作
    • 迭代:模拟中序,逻辑相同
  • 求二叉搜索树的众数
    • 递归:中序,清空结果集的技巧,遍历一遍便可求众数集合
    • [二叉搜索树转成累加树]
    • 递归:中序,双指针操作累加
    • 迭代:模拟中序,逻辑相同

二叉树公共祖先问题

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

二叉搜索树的修改与构造

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

总结

  • 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
  • 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
  • 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了
### 关于代码随想录 Day04 的学习资料与解析 #### 一、Day04 主要内容概述 代码随想录 Day04 的主要内容围绕 **二叉树的遍历** 展开,包括前序、中序和后序三种遍历方式。这些遍历可以通过递归实现,也可以通过栈的方式进行迭代实现[^1]。 #### 二、二叉树的遍历方法详解 ##### 1. 前序遍历(Pre-order Traversal) 前序遍历遵循访问顺序:根节点 -> 左子树 -> 右子树。以下是基于递归的实现: ```python def preorderTraversal(root): result = [] def traversal(node): if not node: return result.append(node.val) # 访问根节点 traversal(node.left) # 遍历左子树 traversal(node.right) # 遍历右子树 traversal(root) return result ``` 对于迭代版本,则可以利用显式的栈来模拟递归过程: ```python def preorderTraversal_iterative(root): stack, result = [], [] current = root while stack or current: while current: result.append(current.val) # 访问当前节点 stack.append(current) # 将当前节点压入栈 current = current.left # 转向左子树 current = stack.pop() # 弹出栈顶元素 current = current.right # 转向右子树 return result ``` ##### 2. 中序遍历(In-order Traversal) 中序遍历遵循访问顺序:左子树 -> 根节点 -> 右子树。递归实现如下: ```python def inorderTraversal(root): result = [] def traversal(node): if not node: return traversal(node.left) # 遍历左子树 result.append(node.val) # 访问根节点 traversal(node.right) # 遍历右子树 traversal(root) return result ``` 迭代版本同样依赖栈结构: ```python def inorderTraversal_iterative(root): stack, result = [], [] current = root while stack or current: while current: stack.append(current) # 当前节点压入栈 current = current.left # 转向左子树 current = stack.pop() # 弹出栈顶元素 result.append(current.val) # 访问当前节点 current = current.right # 转向右子树 return result ``` ##### 3. 后序遍历(Post-order Traversal) 后序遍历遵循访问顺序:左子树 -> 右子树 -> 根节点。递归实现较为直观: ```python def postorderTraversal(root): result = [] def traversal(node): if not node: return traversal(node.left) # 遍历左子树 traversal(node.right) # 遍历右子树 result.append(node.val) # 访问根节点 traversal(root) return result ``` 而迭代版本则稍复杂一些,通常采用双栈法或标记法完成: ```python def postorderTraversal_iterative(root): if not root: return [] stack, result = [root], [] while stack: current = stack.pop() result.insert(0, current.val) # 插入到结果列表头部 if current.left: stack.append(current.left) # 先压左子树 if current.right: stack.append(current.right) # 再压右子树 return result ``` #### 三、补充知识点 除了上述基本的二叉树遍历外,Day04 还可能涉及其他相关内容,例如卡特兰数的应用场景以及组合问题的基础模板[^2][^4]。如果遇到具体题目,可以根据实际需求调用相应算法工具。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值