news 2026/2/9 7:48:05

从零开始刷算法——二叉树篇:验证二叉搜索树 + 二叉树中第k小的元素

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始刷算法——二叉树篇:验证二叉搜索树 + 二叉树中第k小的元素

二叉搜索树(Binary Search Tree, BST)是数据结构中的“秩序守护者”。它的核心定义非常简单:对于任意节点,其左子树所有节点的值 < 当前节点 < 右子树所有节点的值。

这个看似简单的定义,在实际算法落地时衍生出了两个最经典的方向:

  1. 约束(Constraint):如何验证一棵树是否严格遵守了规则?

  2. 顺序(Order):如何利用这个规则快速找到特定的元素?

本文将通过两道经典题目,结合代码实现,深入剖析 BST 的递归哲学。


一、 验证的艺术:自顶向下的区间约束

验证一棵二叉搜索树,最直观的错误想法是:只判断“当前节点”和“左右孩子”的大小关系。这是不够的,因为 BST 要求的是整个左子树都要小于根节点,整个右子树都要大于根节点。

因此,我们需要一种**自顶向下(Top-Down)**的思维:父节点要把自己的“数值范围约束”传递给子节点。

1. 核心代码解析

我们使用递归函数,为每个节点维护一个开区间(left, right)

  • 根节点的范围是(-∞, +∞)

  • 走时:最大值被更新为当前节点的值(不能超过爸爸)。

  • 走时:最小值被更新为当前节点的值(不能小于爸爸)。

C++代码实现:

/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { // 思路: 递归判断左右子树是不是符合我们二叉搜索树的要求 public: // 使用 long long 是为了防止节点值为 INT_MIN 或 INT_MAX 时造成的边界判断错误 bool isValidBST(TreeNode* root, long long left = LLONG_MIN, long long right = LLONG_MAX) { if (root == nullptr) return true; // 核心逻辑: // 1. 当前值必须在 (left, right) 区间内 // 2. 递归左子树:上界变为 root->val // 3. 递归右子树:下界变为 root->val return root->val > left && root->val < right && isValidBST(root->left, left, root->val) && isValidBST(root->right, root->val, right); } };

2. 深度思考:为什么要用long long

代码中leftright的默认值使用了LLONG_MINLLONG_MAX。这是因为 LeetCode 的测试用例中,树节点的值可能正好是int类型的最小值或最大值。 如果使用INT_MIN作为初始下界,当根节点就是INT_MIN时,判断条件root->val > left会变成INT_MIN > INT_MIN(False),导致误判。提升数据类型范围是解决此类边界问题的最佳手段。

3. 时空复杂度分析

  • 时间复杂度:O(N)

    • 我们需要遍历树中的每一个节点来确认其有效性。每个节点只会被访问一次。

  • 空间复杂度:O(H)

    • H 为树的高度。主要消耗在于递归调用栈。

    • 最坏情况(链状树)为 O(N),平均情况(平衡树)为 O(log N)。


二、 顺序的艺术:二叉搜索树中第 K 小的元素

如果说上一题是验证规则,这一题就是利用规则。 BST 的最大特性在于:如果对其进行中序遍历(Inorder Traversal),得到的序列是严格单调递增的。

正如代码思路中所写:“想象把这一棵二叉搜索树拍扁”,它就是一个有序数组[1, 2, 3, 4...]。我们要找第 K 小的数,其实就是中序遍历过程中的第 K 个节点。

1. 核心代码解析

这里不需要构造整个数组,那样太浪费空间。我们利用DFS(深度优先搜索)配合引用传递的计数器k,实现“边走边数,数到即停”。

C++代码实现:

/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), * right(right) {} * }; */ class Solution { // 思路: 想象把这一棵二叉搜索树拍扁, 它刚好会是1234的顺序呈现 // 因此我们优先考虑左边也就是dfs // left,left找完然后找right,这里用&k来维护全局第k小 int ans; // 注意这里的 k 是引用传递 (int& k),这是实现“全局计数”的关键 void dfs(TreeNode* root, int& k) { if (root == nullptr) return; // 1. 先去最左边(最小的地方) dfs(root->left, k); // 2. 中序位置:处理当前节点 // 每经过一个节点,k 减 1,表示这就“划掉”了一个比目标小的数 if (--k == 0) { ans = root->val; return; // 找到了,直接返回,不再继续深搜 } // 3. 左边和自己都不是,那就去右边找 dfs(root->right, k); } public: int kthSmallest(TreeNode* root, int k) { dfs(root, k); return ans; } };

2. 深度思考:引用传递int& k的妙用

很多初学者容易在这里写成值传递int k

  • 值传递:每层递归里的k都是副本,左子树里把k减到了 0,回到父节点时k还是原来的值,导致计数失效。

  • 引用传递int& k使得所有递归层级共享同一个变量。它就像一个倒计时器,无论递归走到哪里,只要遍历了一个节点,这个全局计数器就减 1。

此外,if (--k == 0)后的return虽然不能直接跳出整个递归栈(C++没有直接中断机制),但它能有效阻止当前分支继续向右递归,起到一定的剪枝优化作用。

3. 时空复杂度分析

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

    • 我们需要先深入到最左下角(高度 H),然后开始回溯并计数 k 次。

    • 一旦找到第 k 个数,我们就不再处理剩余的节点了。

    • 在最坏情况下(k = N),复杂度为 O(N)。

  • 空间复杂度:O(H)

    • 主要消耗为递归栈的空间。

    • 最坏情况 O(N),平均情况 O(log N)。


三、 总结

这两道题目完美诠释了 BST 的两面性:

  1. IsValidBST:侧重于**“区间”**。利用递归参数携带状态(最大最小值),自顶向下进行“封锁”检查。

  2. KthSmallest:侧重于**“顺序”**。利用中序遍历的特性,配合全局计数器,自底向上进行“枚举”查找。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/9 6:58:28

Qwen3-VL与Phi-3-Vision对比:边缘设备部署性能评测

Qwen3-VL与Phi-3-Vision对比&#xff1a;边缘设备部署性能评测 1. 背景与选型动机 随着多模态大模型在智能终端、机器人和边缘计算场景中的广泛应用&#xff0c;如何在资源受限的设备上高效部署视觉语言模型&#xff08;VLM&#xff09;成为工程落地的关键挑战。当前&#xf…

作者头像 李华
网站建设 2026/2/8 4:33:17

如何高效完成图片批量抠图?试试科哥CV-UNet大模型镜像

如何高效完成图片批量抠图&#xff1f;试试科哥CV-UNet大模型镜像 1. 背景与痛点分析 在电商、设计、内容创作等领域&#xff0c;图片背景移除&#xff08;即“抠图”&#xff09;是一项高频且耗时的任务。传统方式依赖人工使用Photoshop等工具逐张处理&#xff0c;效率低下&a…

作者头像 李华
网站建设 2026/2/4 17:18:53

AI推理平民化:DeepSeek-R1在普通PC上的运行实测

AI推理平民化&#xff1a;DeepSeek-R1在普通PC上的运行实测 1. 引言 1.1 技术背景与行业痛点 近年来&#xff0c;大语言模型&#xff08;LLM&#xff09;在自然语言理解、代码生成和逻辑推理等任务中展现出惊人能力。然而&#xff0c;主流高性能模型普遍依赖高算力GPU进行推…

作者头像 李华
网站建设 2026/2/7 12:56:24

物联网毕设创新的项目选题汇总

文章目录1前言2 如何选题3 选题方向2.1 嵌入式开发方向2.2 物联网方向2.3 移动通信方向2.4 人工智能方向2.5 算法研究方向2.6 移动应用开发方向2.7 网络通信方向3.4 学长作品展示4 最后1前言 &#x1f947; 近期不少学弟学妹询问学长关于电子信息工程专业相关的毕设选题&#…

作者头像 李华
网站建设 2026/2/7 8:52:27

Glyph医疗影像报告:长描述文本处理部署教程

Glyph医疗影像报告&#xff1a;长描述文本处理部署教程 1. 引言 1.1 业务场景描述 在医疗AI领域&#xff0c;影像报告通常包含大量结构化与非结构化的长文本描述&#xff0c;如CT、MRI等检查的详细病灶分析。传统语言模型受限于上下文长度&#xff08;如8k、32k tokens&…

作者头像 李华
网站建设 2026/2/4 10:37:13

DeepSeek-R1-Distill-Qwen-1.5B避坑指南:常见问题全解析

DeepSeek-R1-Distill-Qwen-1.5B避坑指南&#xff1a;常见问题全解析 1. 引言 随着大模型在边缘设备和本地化部署场景中的需求日益增长&#xff0c;轻量级高性能模型成为开发者关注的焦点。DeepSeek-R1-Distill-Qwen-1.5B 正是在这一背景下脱颖而出的“小钢炮”模型——通过知…

作者头像 李华