← 返回题目列表

如何在二叉搜索树中找到最接近目标值的节点?

高频 简单 第 4 / 27 题 更新于 2026/07/29
二叉搜索树BST最近值搜索路径

简化版

从根开始像普通 BST 搜索一样走,同时维护当前最接近 target 的答案。每到一个节点,就比较 abs(node.val - target) 和当前最小差值;如果 target 小于当前值就去左边,否则去右边。因为另一侧只会离目标方向更远,不必两边都搜。

详细版

BST 最近值的直觉是:搜索目标值时经过的路径,已经包含了最可能接近目标的候选节点。每次访问一个节点都更新答案,然后根据 targetnode.val 的大小决定下一步。如果 target < node.val,更接近 target 的节点只可能在左子树;如果去右子树,值会更大,通常只会离 target 更远。反之同理。

代码可以用迭代实现,维护 ansminDiff。当出现相同差值时,按题目约定返回较小值或任意值;如果题目没有说,面试中最好主动说明 tie-break。时间复杂度 O(h),空间 O(1)。

完整版教学

一、这题和普通查找有什么不同

普通 BST 查找只关心是否存在等于 target 的节点;最近值查找则要求即使没有相等节点,也要返回差值最小的那个。例如树中有 1, 3, 4, 6, 8,目标是 5.2,答案是 6,因为 |6 - 5.2| = 0.8,而 |4 - 5.2| = 1.2

values = [1, 3, 4, 6, 8]
target = 5.2
distance:
  4 -> 1.2
  6 -> 0.8
answer = 6

所以算法要做两件事:沿 BST 方向搜索;同时记录走过路径上的最佳候选。只搜索不记录,会在找不到等值节点时丢失答案。

二、为什么只走一条路径也够

假设当前节点值是 10,目标是 6.3。因为目标比 10 小,真正接近目标的节点如果还没遇到,大概率在左子树;右子树所有值都大于 10,它们离 6.3 至少比 10 更远。因此可以直接去左边,同时把 10 作为一个候选保留下来。

node.val = 10, target = 6.3
right subtree values > 10
任意 right 值 x: x - 6.3 > 10 - 6.3 = 3.7
所以右子树不可能比 10 更接近 target

这个推理依赖 BST 的全局有序性。每次只排除一侧,并不是贪心乱猜,而是利用“被排除那侧的所有值都在当前值外侧”的数学事实。

三、用路径模拟一遍

看下面这棵 BST,找最接近 target = 4.6 的节点。

        5
       / \
      3   8
     / \
    2   4

路径:5 -> 3 -> 4

访问 5 时,差值是 0.4,暂定答案 5;因为 4.6 < 5,去左边。访问 3 时,差值是 1.6,不更新;因为 4.6 > 3,去右边。访问 4 时,差值是 0.6,仍不更新;继续向右为空,结束。答案是 5。这个例子很适合说明:最终停下的叶子不一定是答案,必须维护全程最佳值。

四、标准迭代代码

代码要避免浮点比较写得含糊。可以维护 double bestDiff,每次访问都计算当前差值。如果题目要求差值相同返回较小值,就在相等时补 tie-break。

int closestValue(TreeNode root, double target) {
    int ans = root.val;
    double bestDiff = Math.abs(root.val - target);
    TreeNode cur = root;

    while (cur != null) {
        double diff = Math.abs(cur.val - target);
        if (diff < bestDiff || (diff == bestDiff && cur.val < ans)) {
            ans = cur.val;
            bestDiff = diff;
        }

        if (target < cur.val) {
            cur = cur.left;
        } else if (target > cur.val) {
            cur = cur.right;
        } else {
            return cur.val;
        }
    }
    return ans;
}

如果语言里浮点相等比较不稳定,tie-break 可以写成 Math.abs(diff - bestDiff) < 1e-12。不过很多面试题的输入能让直接比较通过,讲清楚意图即可。

五、和中序数组法的对比

这题也可以先中序遍历得到升序数组,再二分找插入位置,比较相邻两个数。但单次查询时,这样会多用 O(n) 空间,并且要遍历全树。路径搜索法只走树高 h,平衡时明显更轻。

方法时间复杂度空间复杂度适用场景
中序数组 + 二分O(n) 构建 + O(log n) 查询O(n)同一棵树大量查询且可缓存数组
单路径搜索O(h)O(1)单次或少量查询
增强平衡树O(log n)结构额外维护动态更新和大量查询

如果面试官问“多次 target 查询怎么办”,可以说明:若树不变,先中序转数组,再对每个 target 二分,整体会更划算;若树动态变化,则需要平衡树结构支持。

六、常见误区与追问

最近值题的关键不是走到哪里停,而是一路走一路更新最佳候选。

  • 误区:最后搜索停下来的父节点一定是最近值。 不一定,路径上更早遇到的节点可能更接近,例如 target=4.6 时答案是 5,不是最后访问的 4。
  • 误区:需要同时搜索左右子树。 BST 的有序性允许排除一侧,单路径搜索已经足够。
  • 误区:只在空指针处才更新答案。 每访问一个节点都要更新,否则可能漏掉根或中间节点。
  • 追问:差值相同返回谁? 如果题目没约定,要主动说明;常见处理是返回较小值或任意一个。
  • 追问:复杂度为什么是 O(h)? 每层只走一个分支,最多走树高个节点。
  • 追问:如果要找最接近的 k 个值呢? 可以中序得到有序序列后双指针,或用两个栈分别维护前驱和后继。

七、加强记忆

BST 最近值可以理解成“带备忘录的搜索”:搜索方向仍然按 target < node.val 向左、target > node.val 向右,但每经过一个节点都要用差值更新答案。被排除的一侧不是随便放弃,而是因为它的所有值都在当前节点更外侧,不可能比当前节点更接近目标。回答时把“单路径 + 持续更新 + O(h)/O(1) + tie-break”说完整,就能覆盖主要考点。