当前位置: 首页 > news >正文

669. 修剪二叉搜索树

目录

题目链接:

题目:

解题思路:

代码:

总结:


题目链接:

669. 修剪二叉搜索树 - 力扣(LeetCode)

题目:

解题思路:

使用后序遍历,从根部开始往上一次处理,然后就是上一个博客一样的处理情况

代码:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if(root==null) return null;root.left=trimBST(root.left,low,high);root.right=trimBST(root.right,low,high);if(root.val<low||root.val>high){if(root.left==null&&root.right==null) return n                                                                                                                                          ull;else if(root.left!=null&&root.right==null) return root.left;else if(root.left==null&&root.right!=null) return root.right;else{TreeNode node=root.right;while(node.left!=null) node=node.left;node.left=root.left;return root.right;}}return root;}
}

深入解析二叉搜索树修剪算法(trimBST)的实现与原理
在二叉搜索树(BST)的操作中,修剪操作是一个重要的功能,它能够移除所有值不在指定范围内的节点,同时保持 BST 的特性。本文将详细解析一段二叉搜索树修剪算法的代码,探讨其实现逻辑、执行流程以及背后的设计思想。
二叉搜索树修剪的基本概念
二叉搜索树的修剪(trim)指的是:给定一个范围 [low, high],移除二叉搜索树中所有值小于 low 或大于 high 的节点,并且保证修剪后的树仍然是一棵有效的二叉搜索树。
修剪操作需要满足两个核心要求:
修剪后的树只包含值在 [low, high] 范围内的节点
必须保持二叉搜索树的特性(左子树值 < 根节点值 < 右子树值)
代码整体结构
java
运行
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null) return null;
        root.left=trimBST(root.left,low,high);
        root.right=trimBST(root.right,low,high);
        if(root.val<low||root.val>high){
            if(root.left==null&&root.right==null) return null;
            else if(root.left!=null&&root.right==null) return root.left;
            else if(root.left==null&&root.right!=null) return root.right;
            else{
                TreeNode node=root.right;
                while(node.left!=null) node=node.left;
                node.left=root.left;
                return root.right;
            }
        }
        
        return root;
    }
}
这段代码采用递归方式实现二叉搜索树的修剪,整体逻辑分为三个主要步骤:
递归修剪左子树
递归修剪右子树
判断并处理当前节点是否需要被修剪
关键代码逐行解析
递归终止条件
java
运行
if(root==null) return null;
这是递归的基本终止条件。当遇到空节点时,直接返回 null,表示这个位置不需要任何节点。
修剪左子树
java
运行
root.left=trimBST(root.left,low,high);
这行代码递归地修剪当前节点的左子树,并将修剪后的左子树结果重新赋值给当前节点的左指针。这确保了左子树中所有不符合条件的节点都已被移除。
修剪右子树
java
运行
root.right=trimBST(root.right,low,high);
类似地,这行代码递归地修剪当前节点的右子树,并将修剪后的结果重新赋值给当前节点的右指针。
判断当前节点是否需要修剪
java
运行
if(root.val<low||root.val>high){
    // 处理当前节点需要被修剪的情况
}
这是修剪操作的核心判断:如果当前节点的值小于 low 或大于 high,则该节点需要被移除(修剪掉)。
根据当前节点被移除后,其左右子树的不同情况,代码分为四种处理方式:
情况 1:左右子树都为空
java
运行
if(root.left==null&&root.right==null) return null;
如果当前节点是叶子节点(左右子树都为空),且需要被修剪,则直接返回 null,表示这个位置不再有节点。
情况 2:只有左子树不为空
java
运行
else if(root.left!=null&&root.right==null) return root.left;
如果当前节点需要被修剪,且只有左子树存在,则返回左子树。这相当于用左子树替代了当前节点的位置,保持了树的连续性。
情况 3:只有右子树不为空
java
运行
else if(root.left==null&&root.right!=null) return root.right;
类似地,如果当前节点需要被修剪,且只有右子树存在,则返回右子树,用右子树替代当前节点的位置。
情况 4:左右子树都不为空
java
运行
else{
    TreeNode node=root.right;
    while(node.left!=null) node=node.left;
    node.left=root.left;
    return root.right;
}
这是最复杂的一种情况,当前节点需要被修剪,且左右子树都存在。处理方式如下:
找到右子树中最左侧的节点(即右子树中值最小的节点)
将当前节点的左子树连接到这个最小节点的左指针上
返回当前节点的右子树作为新的子树根节点
这种处理方式保证了修剪后仍然维持二叉搜索树的特性:原左子树的所有值都小于右子树最小节点的值,因此可以安全地连接到其左子树。
保留当前节点
java
运行
return root;
如果当前节点的值在 [low, high] 范围内,则不需要修剪,直接返回当前节点。
算法执行流程示例
为了更好地理解修剪过程,我们通过一个具体示例来模拟算法的执行:
假设初始二叉搜索树结构如下:
plaintext
        3
       / \
      0   4
       \
        2
       /
      1
修剪范围为 [1, 3](即保留值在 1 到 3 之间的节点)
执行流程:
从根节点 3 开始,首先递归修剪左子树(根为 0)和右子树(根为 4)
修剪左子树(根为 0):
递归修剪 0 的左子树(null)和右子树(根为 2)
修剪右子树(根为 2):
递归修剪 2 的左子树(根为 1)和右子树(null)
修剪左子树(根为 1):
1 的值在范围内,返回 1
2 的值在范围内,返回 2(其左子树已修剪为 1)
0 的值(0 < 1)需要被修剪:
0 的右子树不为空(根为 2)
返回右子树(根为 2)
根节点 3 的左子树更新为 2
修剪右子树(根为 4):
4 的值(4 > 3)需要被修剪:
4 的左右子树都为空
返回 null
根节点 3 的右子树更新为 null
处理根节点 3:
3 的值在范围内,不需要修剪
返回 3
最终修剪后的树结构:
plaintext
      3
     /
    2
   /
  1
算法设计思想分析
后序遍历的应用
该算法采用了后序遍历的思想:先处理左子树,再处理右子树,最后处理当前节点。这种顺序的优势在于:
在判断当前节点是否需要修剪前,左右子树已经完成了修剪
处理当前节点时,可以直接使用修剪后的左右子树结果
确保了所有子树都已符合条件后,再决定当前节点的去留
利用 BST 特性的优化
虽然代码没有显式地利用 BST"左小右大" 的特性进行剪枝,但在处理左右子树都存在的情况时,通过寻找右子树最小值节点来连接左子树,正是利用了 BST 的特性,保证了修剪后的树仍然是有效的 BST。
递归的优势
递归实现使得代码简洁明了,每一层递归只关注当前节点的处理,符合 "分而治之" 的思想:
将大问题(修剪整棵树)分解为小问题(修剪左子树、右子树和当前节点)
每个小问题的解决方式相同
通过递归调用自底向上地构建解决方案
时间复杂度与空间复杂度
时间复杂度
算法的时间复杂度为 O (n),其中 n 是二叉树的节点总数。在最坏情况下,需要访问树中的每个节点一次,例如当所有节点都需要被修剪或所有节点都需要被保留时。
空间复杂度
空间复杂度为 O (h),其中 h 是二叉树的高度。这是由于递归调用栈的深度最多为树的高度:
在平衡二叉树中,h = log n,空间复杂度为 O (log n)
在最坏情况下(树退化为链表),h = n,空间复杂度为 O (n)
与其他修剪方法的对比
另一种常见的修剪思路是先判断当前节点是否需要保留,再决定修剪方向:
如果当前节点值 < low:直接返回右子树的修剪结果
如果当前节点值 > high:直接返回左子树的修剪结果
否则:分别修剪左右子树
两种方法各有优势:
本文解析的方法采用后序遍历,逻辑上更符合 "先处理子树再处理自身" 的思维
另一种方法更积极地利用 BST 特性,可以提前终止某些子树的遍历,在某些情况下更高效
但两种方法的时间复杂度和空间复杂度是相同的。
可能的错误与边界情况
空树处理:代码正确处理了空树情况(root == null 时返回 null)
单节点树:如果单节点值在范围内则保留,否则返回 null
所有节点都需要修剪:最终返回 null
所有节点都不需要修剪:返回原树结构
边界值处理:值等于 low 或 high 的节点会被保留
总结
本文解析的二叉搜索树修剪算法通过后序遍历的递归方式,实现了对二叉搜索树的高效修剪。算法的核心思想是:先递归修剪左右子树,再根据当前节点值决定是否保留该节点。当需要移除节点时,根据其左右子树的存在情况,采用不同的处理策略,确保修剪后的树仍然保持二叉搜索树的特性。
该算法的优势在于:
逻辑清晰,实现简洁
正确处理了所有可能的节点情况
保持了二叉搜索树的特性
时间和空间复杂度均处于较优水平
理解这个算法不仅有助于掌握二叉搜索树的修剪操作,也能加深对后序遍历和递归思想的理解,为处理更复杂的树结构问题提供参考。

总结:

本文解析了二叉搜索树的修剪算法trimBST,该算法通过后序遍历递归处理,先修剪左右子树再判断当前节点。核心逻辑是:若节点值超出范围[low,high],则根据子树情况执行不同处理策略:当左右子树均存在时,将左子树连接到右子树的最小节点下。该算法时间复杂度O(n),空间复杂度O(h),能有效保持BST特性,正确处理空树、单节点等边界情况,体现了分治思想和BST特性的巧妙运用。

http://www.dtcms.com/a/394144.html

相关文章:

  • 大QMT自动可转债申购
  • PolarCTF PWN 网络安全2023秋季个人挑战赛刷题
  • MySQL-day4_02(事务)
  • JUC(8)线程安全集合类
  • springboot中@EnableAsync有什么作用
  • Spark专题-第二部分:Spark SQL 入门(6)-算子介绍-Generate
  • C#练习题——Dictionary
  • Feign
  • SPA小说集之三《森林城市反甩锅战:ERP的权责边界》
  • Qt(模态对话框和非模态对话框)
  • 【无标题】物联网 frid卡控制
  • 【LLM LangChain】 模型绑定工具+调用工具(手动调用/LangGraph/AgentExecutor)+相关注意事项
  • 图神经网络(GNN)入门:用PyG库处理分子结构与社会网络
  • 【C++】编码表 STL简介:STL是什么,版本,六大组件,重要性以及学习方法总结
  • show_interrupts函数的进一步解析及irq_desc结构体
  • Kafka面试精讲 Day 19:JVM调优与内存管理
  • 10.vector容器
  • Linux系统介绍
  • MFC中的CMFCDynamicLayout类的介绍
  • UniScene 统一驾驶场景 | 生成语义占据 | 生成多视角视频 | 生成激光点云 CVPR2025
  • Git 简明教程:从原理到实战
  • 【设计模式】中介者模式
  • nginx添加modsecurity插件
  • 代码上传Github:SSH法
  • 【iOS】AFNetworking初步了解及使用
  • JVM实战-G1参数调优
  • 超简单的视频分割脚本
  • 基于51单片机电子钟闹钟12/24小时制LCD显示( proteus仿真+程序+设计报告+讲解视频)
  • 在 Windows 系统上安装官方 Codex CLI 教程
  • Redis 配置与优化全攻略