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

【LeetCode 热题 100】230. 二叉搜索树中第 K 小的元素——中序遍历

Problem: 230. 二叉搜索树中第 K 小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(H + k)
    • 空间复杂度:O(H)

整体思路

这段代码旨在解决一个经典的二叉搜索树(BST)问题:二叉搜索树中第K小的元素 (Kth Smallest Element in a BST)。其目标是在一个有效的BST中,找到按升序排列的第 k 个节点的值。

该算法巧妙地利用了BST的一个核心性质:BST的中序遍历结果是一个严格递增的有序序列。因此,寻找第 k 小的元素就等价于寻找中序遍历序列中的第 k 个元素。

算法的核心思路是执行一次受控的中序遍历:

  1. 利用中序遍历的顺序

    • 算法通过一个递归的 dfs 函数来实现深度优先搜索,其结构 dfs(left) -> process(node) -> dfs(right) 正是中序遍历的模式。
    • 这意味着节点被处理的顺序(即 process(node) 被执行的顺序)是严格按照节点值从小到大的顺序。
  2. 计数与提前终止

    • 为了找到第 k 个元素,算法使用一个计数器 cnt。它被初始化为 k
    • 在(隐式的)中序遍历过程中,每当处理一个节点(即从左子树返回后),就将 cnt 减一。
    • cnt 减到 0 时,说明当前处理的这个节点就是我们正在寻找的第 k 小的元素。
    • 此时,将其值存入结果变量 ans 中。
  3. 剪枝(隐式)

    • 虽然代码结构上看起来会遍历整棵树,但一旦 ans 被赋值,后续的 dfs 调用虽然会继续执行,但它们不会再改变 ans 的值。
    • 一个更优化的版本可以在找到答案后通过抛出异常或返回特殊标志来提前终止整个递归过程,但这会使代码稍微复杂一些。当前版本的实现虽然不提前终止,但逻辑正确且易于理解。
  4. 状态管理

    • cntans 被声明为类成员变量,以便在 dfs 的递归调用之间共享和修改它们的状态。kthSmallest 方法负责初始化 cnt 并启动递归,最后返回 ans

完整代码

/*** 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 {// cnt: 倒数计数器,从 k 开始递减。private int cnt;// ans: 用于存储最终找到的第 k 小的元素的值。private int ans;/*** 在二叉搜索树中查找第 k 小的元素。* @param root BST的根节点* @param k 要查找的元素的排名(从1开始)* @return 第 k 小的元素的值*/public int kthSmallest(TreeNode root, int k) {// 初始化计数器为 kthis.cnt = k;// 启动中序遍历的递归过程dfs(root);// 返回在 dfs 过程中找到的结果return this.ans;}/*** 递归辅助函数,执行受控的中序遍历。* @param node 当前访问的节点*/private void dfs(TreeNode node) {// 基本情况:如果节点为空,则返回。if (node == null) {return;}// 1. 递归地访问左子树dfs(node.left);// --- 中序遍历的核心处理逻辑 ---// 2. 处理当前节点// 每处理一个节点,计数器减一this.cnt--;// 检查是否找到了第 k 个元素if (this.cnt == 0) {// 如果 cnt 变为 0,说明当前节点就是第 k 小的元素this.ans = node.val;// 注意:虽然找到了答案,但此版本的递归不会立即停止,// 而是会完成右子树的空转。// 可以在此处 return 来做一个简单的剪枝。return; }// 3. 递归地访问右子树// 如果已经找到答案 (cnt <= 0),可以避免进入右子树来优化if (cnt > 0) {dfs(node.right);}}
}

时空复杂度

时间复杂度:O(H + k)

  1. 节点访问:算法通过中序遍历的方式访问节点。它会首先深入到最左边的叶子节点,然后开始回溯并向右访问。
  2. 访问路径
    • 为了找到第 k 小的元素,算法需要完整地遍历包含这 k 个元素的左侧子树。
    • 在最坏的情况下(例如,k=N),算法需要遍历整棵树,时间复杂度为 O(N)。
    • 在一般情况下,算法会访问从根节点到第 k 小元素路径上的所有节点,以及这些路径节点的部分子树。
    • 具体来说,它会访问到第 k 小的元素为止。这个过程涉及的节点数大致等于从根到第 k 小元素的路径长度(最坏为 H,树的高度)加上 k
    • 因此,一个更精确的时间复杂度是 O(H + k)
  3. 特殊情况
    • 如果树是平衡的,H 约等于 log N,复杂度为 O(log N + k)。
    • 如果树是退化的链表,H 约等于 N,复杂度为 O(N + k) = O(N)。
    • 由于 k 最大可以是 N,所以最坏时间复杂度是 O(N)。

空间复杂度:O(H)

  1. 主要存储开销:算法的额外空间主要由递归调用栈所占用。
  2. 递归深度:递归的最大深度取决于树的高度 H
    • 最好情况:如果树是高度平衡的,其高度 H 约为 log N。此时空间复杂度为 O(log N)
    • 最坏情况:如果树是极度不平衡的(例如,退化成一个链表),其高度 H 将等于节点数 N。此时空间复杂度为 O(N)
  3. 类成员变量cntans 占用的空间是 O(1)。

综合分析
该算法的(辅助)空间复杂度由递归栈的深度决定,因此为 O(H),其中 H 是树的高度。

http://www.lryc.cn/news/591474.html

相关文章:

  • Java全栈面试实录:从电商支付到AIGC的深度技术挑战
  • HTML常用标签汇总(精简版)
  • Easy ARM2132
  • 测试学习之——Pytest Day3
  • 【git】使用教程
  • HTTP 状态码笔记
  • element-plus——图标推荐
  • milvus向量数据库连接测试 和 集合维度不同搜索不到内容
  • 嵌入式时钟系统
  • C++ 返回值优化(Return Value Optimization, RVO)
  • c++列表初始化
  • MyUI轮播Carousel组件文档
  • Windows10笔记本电脑开启BIOS
  • deep learning(李宏毅)--(六)--loss
  • “显著性”(Saliency)是计算机视觉中的一个重要概念,主要指的是图像或视频中最吸引人注意力的区域或对象
  • 川翔云电脑:云端算力新标杆,创作自由无边界
  • 产品经理如何绘制流程图
  • 4.PCL点云的数据结构
  • 上证50etf期权交易限制的是什么?
  • 【JAVA新特性】Java 8 新特性实战
  • 小程序性能优化全攻略:提升用户体验的关键策略
  • Java List 集合详解:从基础到实战,掌握 Java 列表操作全貌
  • Kubernetes 学习笔记
  • 【JEECG 组件扩展】JSwitch开关组件扩展单个多选框样式
  • 基于pytorch深度学习笔记:1.LeNetAlexNet
  • XXE漏洞4-XXE无回显文件读取-PentesterLab靶场搭建
  • Kotlin密封类
  • 6. 工程化实践类:《Webpack 5 性能优化全指南:从构建速度到输出质量》
  • 如何成为高级前端开发者:系统化成长路径。
  • 自动化测试工具 Selenium 入门指南