力扣解题-530. 二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
示例 2:
输入:root = [1,0,48,null,null,12,49]
输出:1
提示:
树中节点的数目范围是 [2, 10⁴]
0 <= Node.val <= 10⁵
Related Topics
树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树
第一次解答(核心最优解)
解题思路
核心方法:二叉搜索树(BST)中序遍历法,利用BST“中序遍历结果为升序序列”的核心特性,只需比较升序序列中相邻节点的差值,即可找到全局最小绝对差(无需比较所有节点对),时间复杂度O(n)、空间复杂度O(h)(h为树的高度),是本题的最优解法。
核心逻辑拆解
BST的最小绝对差的核心规律:
- BST关键特性:中序遍历BST得到的节点值序列是严格升序的(如示例1中序遍历结果为[1,2,3,4,6]);
- 最小差值规律:升序序列中,最小绝对差一定出现在相邻元素之间(如[1,2,3,4,6]中,2-1=1、3-2=1、4-3=1、6-4=2,最小差值为1);
- 算法核心:通过中序遍历遍历BST,记录前一个节点值,实时计算当前节点与前一个节点的差值,更新最小差值。
具体执行逻辑
- 初始化变量:
prev:记录中序遍历的前一个节点值,初始为null(无前置节点);minDiff:记录最小绝对差,初始化为Integer.MAX_VALUE(保证首次比较一定会更新);
- 中序遍历递归:
- 递归终止条件:当前节点
node == null,直接返回; - 先递归遍历左子树(中序遍历“左”);
- 处理当前节点:若
prev != null,计算当前节点值与prev的差值,更新minDiff为“当前minDiff”和“该差值”的较小值; - 更新
prev为当前节点值(为下一个节点比较做准备); - 递归遍历右子树(中序遍历“右”);
- 递归终止条件:当前节点
- 返回结果:遍历完成后,
minDiff即为全局最小绝对差。
执行流程可视化(以示例1 root=[4,2,6,1,3]为例)
| 遍历节点 | prev值 | 差值计算 | minDiff更新 | 最终prev |
|---|---|---|---|---|
| 1 | null | 无 | MAX_VALUE | 1 |
| 2 | 1 | 2-1=1 | 1 | 2 |
| 3 | 2 | 3-2=1 | 1 | 3 |
| 4 | 3 | 4-3=1 | 1 | 4 |
| 6 | 4 | 6-4=2 | 1 | 6 |
最终返回minDiff=1,符合示例1结果。 |
关键细节说明
- 差值无需取绝对值:因中序遍历是升序序列,
node.val >= prev,差值node.val - prev天然为正,无需额外调用Math.abs(); - 变量作用域:
prev和minDiff定义为类成员变量(或方法内局部变量+数组封装),保证递归过程中值的连续性; - 空间优化:无需存储完整的中序遍历序列,仅需记录前一个节点值,空间复杂度从O(n)降至O(h);
- 边界处理:题目保证节点数≥2,无需处理只有单个节点的场景。
性能说明
- 时间复杂度:O(n)(每个节点仅被访问一次,中序遍历的线性复杂度);
- 空间复杂度:O(h)(递归栈深度等于树的高度,平衡BST为O(logn),斜树为O(n));
- 优势:
- 利用BST特性,将“比较所有节点对”的O(n²)复杂度降至O(n);
- 空间效率最优,无需额外存储完整序列;
- 递归逻辑简洁,贴合BST中序遍历的经典范式。
private Integer prev = null; // 记录中序遍历的前一个节点值
private int minDiff = Integer.MAX_VALUE; // 初始化为最大整数
public int getMinimumDifference(TreeNode root) {
inorder(root);
return minDiff;
}
public void inorder(TreeNode node) {
if (node == null) {
return;
}
inorder(node.left);
if (prev != null) {
minDiff=Math.min(minDiff,node.val-prev);
}
prev=node.val;
inorder(node.right);
}
示例解答
解题思路
解法1:中序遍历迭代法(非递归,避免栈溢出)
核心方法:使用栈模拟中序遍历的递归过程,同样记录前一个节点值并计算相邻差值,逻辑与递归法一致,避免递归栈溢出风险(如斜树场景)。
代码实现
public int getMinimumDifference(TreeNode root) {
int minDiff = Integer.MAX_VALUE;
Integer prev = null;
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
// 中序遍历迭代模板
while (curr != null || !stack.isEmpty()) {
// 遍历到左子树最深处
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
// 弹出并处理当前节点
curr = stack.pop();
if (prev != null) {
minDiff = Math.min(minDiff, curr.val - prev);
}
prev = curr.val;
// 遍历右子树
curr = curr.right;
}
return minDiff;
}
核心逻辑说明
- 栈模拟递归:通过栈存储待处理的节点,先遍历到左子树最深处,再弹出节点处理,最后遍历右子树;
- 状态保持:
prev和minDiff为局部变量,无需类成员变量,代码封装性更好; - 终止条件:
curr == null且栈为空时,遍历结束。
性能说明
- 时间复杂度:O(n)(与递归法一致);
- 空间复杂度:O(h)(栈深度等于树的高度);
- 优势:
- 非递归实现,避免极端斜树导致的递归栈溢出;
- 变量为局部变量,无线程安全问题;
- 劣势:代码量略多于递归法,需记忆中序遍历迭代模板。
解法2:存储完整中序序列法(直观但空间稍高)
核心方法:先通过中序遍历将BST节点值存入列表(升序),再遍历列表计算相邻元素的最小差值,逻辑最直观,适合新手理解。
代码实现
public int getMinimumDifference(TreeNode root) {
List<Integer> list = new ArrayList<>();
// 中序遍历收集所有节点值
inorderCollect(root, list);
int minDiff = Integer.MAX_VALUE;
// 遍历升序列表,计算相邻差值
for (int i = 1; i < list.size(); i++) {
minDiff = Math.min(minDiff, list.get(i) - list.get(i-1));
}
return minDiff;
}
private void inorderCollect(TreeNode node, List<Integer> list) {
if (node == null) {
return;
}
inorderCollect(node.left, list);
list.add(node.val);
inorderCollect(node.right, list);
}
核心逻辑说明
- 两步操作:先收集所有节点值到升序列表,再遍历列表找最小相邻差值;
- 直观易懂:无需理解“相邻差值即最小差值”的推导,仅需知道BST中序遍历是升序;
- 兼容性强:即使不是BST,也可通过排序后找最小差值(但本题利用BST特性可优化)。
性能说明
- 时间复杂度:O(n)(中序遍历O(n) + 列表遍历O(n));
- 空间复杂度:O(n)(需存储所有节点值);
- 优势:
- 逻辑最直观,新手易理解;
- 代码分步清晰,调试方便;
- 劣势:空间复杂度高于前两种解法,需额外存储完整序列。
总结
- 中序遍历递归法(核心解):O(n)时间+O(h)空间,利用BST特性实现最优解,代码简洁高效;
- 中序遍历迭代法:O(n)时间+O(h)空间,非递归实现,避免栈溢出,适合高树高场景;
- 存储完整序列法:O(n)时间+O(n)空间,逻辑直观,适合新手理解;
- 关键技巧:
- 核心思想:BST中序遍历为升序序列,最小绝对差必出现在相邻节点之间;
- 性能优化:无需存储完整序列,仅记录前一个节点值即可将空间复杂度从O(n)降至O(h);
- 差值简化:升序序列中相邻差值天然为正,无需取绝对值。