全栈开发知识库

86.二叉搜索树的最小绝对差

题目

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

示例 1:

  • 输入:root = [4,2,6,1,3]
  • 输出:1

示例 2:

  • 输入:root = [1,0,48,null,null,12,49]
  • 输出:1

提示:

  • 树中节点的数目范围是 [2, 104]
  • 0 <= Node.val <= 105

思路

  • 第 0 步:暴力法(想一下就好,别真写)

把所有节点值收集起来,两两求差取最小。O(n²) 时间。能过(n ≤ 10^4),但完全浪费了"BST"这个条件。

  • 第 1 步:先想清楚一件事——最小差一定来自"排序后相邻的两个数"

这是本题的核心洞察。

假设把所有值排成升序 a₀ < a₁ < a₂ < ... < aₙ₋₁。任取两个不相邻的数 aᵢ 和 aⱼ(j > i+1),它们的差是:

aⱼ - aᵢ = (aⱼ - aⱼ₋₁) + (aⱼ₋₁ - aᵢ)
        ≥ (aⱼ - aⱼ₋₁)          ← 因为后一项必然 > 0

也就是说,跨过任何中间元素去比较,差值只会变大。所以最小差必然出现在排序后的相邻位置。

于是问题从"检查 n² 对"缩减到"检查 n-1 对"。

  • 第 2 步:BST 送了你一个免费的有序数组

到这里如果是普通二叉树,你还得先把值收集起来排序,O(n log n)。

但这是 BST,它有一条铁律:

▎ 中序遍历(左 → 根 → 右)BST,得到的序列必然是严格升序的。

为什么?因为 BST 的定义就是"左子树全部 < 根 < 右子树全部"。

中序遍历恰好按这个顺序访问:先访问所有比我小的、再是我、再是所有比我大的。递归下去,整体就是升序。

          4
        /   \
       2     6
      / \
     1   3

  中序遍历:1 → 2 → 3 → 4 → 6 ✅ 天然有序,不用排序。
  • 第 3 步:合并成最终解法

最简单的算法就是中序遍历,然后把所有数据存数组,再循环遍历一遍找出相邻的最小的差就可以了,但是这样会造成空间复杂度对数组的浪费,完全可以用一个prev变量来记录上一个的值。

既然中序遍历直接给出升序序列,那就边遍历边比较相邻两个,连数组都不用存:

▎ 中序遍历,用一个变量 prev 记住上一个访问到的值。每到一个新节点,算 当前值 - prev,更新全局最小值。

为什么不用取绝对值? 因为中序序列是升序的,当前值 - prev 必然 ≥ 0。这也是 BST 相比普通树省下来的一点好处。

于是每个节点的视角都极其简单:

          4        ← 4 说:"左边的 2 那一片先来(会给出 1,2,3),
        /   \              然后我,然后 6 那一片。"
       2     6
      / \
     1   3        ← 2 说:"左边的 1 先来,然后我,然后右边的 3。"
                          1 说:"左边空的,那就我,右边也空的。"

每个节点都在说同一句话。这就是递归。

  class Solution2 {

      fun getMinimumDifference(root: TreeNode?): Int {
          val values = mutableListOf<Int>()

          // 第 1 步:纯粹的中序遍历,把值按升序倒进数组,不掺任何业务逻辑
          fun inorder(node: TreeNode?) {
              if (node == null) return
              inorder(node.left)
              values.add(node.`val`)       // 唯一的动作:记下来
              inorder(node.right)
          }
          inorder(root)

          // 第 2 步:现在 values 已经是升序数组了,问题变成"数组相邻两项最小差"
          //         从下标 1 开始,和前一项比,完全没有递归了
          var minDiff = Int.MAX_VALUE
          for (i in 1 until values.size) {
              minDiff = minOf(minDiff, values[i] - values[i - 1])
          }
          return minDiff
      }
  }

最好的写法:

class TreeNode(var `val`: Int) {
    var left: TreeNode? = null
    var right: TreeNode? = null
}
class Solution {

    // 状态放在类上 —— 所有递归层访问的都是同一份
    private var prev: Int? = null
    private var minDiff = Int.MAX_VALUE

    fun getMinimumDifference(root: TreeNode?): Int {
        // ⚠️ 关键:入口处必须重置!
        //    否则同一个 Solution 实例第二次被调用时,会带着上一次的残留状态
        prev = null
        minDiff = Int.MAX_VALUE

        inorder(root)
        return minDiff
    }

    // 独立的私有方法,不再嵌套
    private fun inorder(node: TreeNode?) {
        if (node == null) return

        inorder(node.left)                                   // ① 左

        prev?.let { minDiff = minOf(minDiff, node.`val` - it) }  // ② 根
        prev = node.`val`

        inorder(node.right)                                  // ③ 右
    }
}

复杂度

  • 时间复杂度: O(n)
  • 空间复杂度: O(h)


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记