news 2026/2/24 0:03:50

从零开始刷算法——二叉树篇:验证二叉搜索树 + 二叉树中第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/16 16:34:42

Qwen3-VL图像理解保姆级教程:没GPU也能3步跑通

Qwen3-VL图像理解保姆级教程&#xff1a;没GPU也能3步跑通 你是不是也遇到过这种情况&#xff1f;研究生导师突然说&#xff1a;“下周组会&#xff0c;把Qwen3-VL的论文效果复现一下。”你一查资料&#xff0c;好家伙&#xff0c;模型参数几十亿&#xff0c;推理要显存、训练…

作者头像 李华
网站建设 2026/2/20 11:34:23

Qwen3-Embedding-0.6B推理慢?GPU算力优化部署实战详解

Qwen3-Embedding-0.6B推理慢&#xff1f;GPU算力优化部署实战详解 1. 背景与问题提出 在当前大模型驱动的语义理解场景中&#xff0c;文本嵌入&#xff08;Text Embedding&#xff09;作为信息检索、推荐系统和语义匹配的核心组件&#xff0c;其性能直接影响下游任务的效率与…

作者头像 李华
网站建设 2026/2/13 17:12:09

微信插件管理新策略:WeChatExtension-ForMac重构部署方案

微信插件管理新策略&#xff1a;WeChatExtension-ForMac重构部署方案 【免费下载链接】WeChatExtension-ForMac Mac微信功能拓展/微信插件/微信小助手(A plugin for Mac WeChat) 项目地址: https://gitcode.com/gh_mirrors/we/WeChatExtension-ForMac 您是否正在寻找更灵…

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

MinerU是否需要微调?预训练模型适用场景详解

MinerU是否需要微调&#xff1f;预训练模型适用场景详解 1. 引言&#xff1a;PDF信息提取的挑战与MinerU的定位 在现代数据处理流程中&#xff0c;非结构化文档&#xff08;尤其是PDF&#xff09;的信息提取是一项高频且关键的任务。传统方法在面对多栏排版、复杂表格、数学公…

作者头像 李华
网站建设 2026/2/9 14:18:19

AUTOSAR运行时环境详解:新手友好版说明

AUTOSAR运行时环境详解&#xff1a;从“搭积木”说起你有没有想过&#xff0c;现代一辆高端汽车里&#xff0c;为什么能同时实现自动巡航、车道保持、智能空调、远程诊断这么多复杂功能&#xff0c;而它们之间还不会“打架”&#xff1f;背后的关键&#xff0c;并不只是硬件堆得…

作者头像 李华
网站建设 2026/2/23 1:24:51

DS4Windows终极指南:让PS4/PS5手柄在PC上完美运行

DS4Windows终极指南&#xff1a;让PS4/PS5手柄在PC上完美运行 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows DS4Windows是一款免费开源的PlayStation手柄映射工具&#xff0c;能让你的PS…

作者头像 李华