当前位置: 首页 > news >正文

leetcode98.验证二叉搜索树:迭代法中序遍历与栈操作的深度剖析

一、题目深度解析与BST核心定义

题目描述

验证二叉搜索树(BST)是算法中的经典问题,要求我们判断给定的二叉树是否为有效的二叉搜索树。根据定义,二叉搜索树需满足以下条件:

  1. 左子树上所有节点的值均严格小于根节点的值
  2. 右子树上所有节点的值均严格大于根节点的值
  3. 左右子树也必须为二叉搜索树

BST的本质特性

  • 中序遍历性质:二叉搜索树的中序遍历结果是一个严格递增的序列。例如:
        2/ \1   3
    中序遍历结果:[1, 2, 3](严格递增)
    
  • 递归定义:每个节点需同时满足左右子树的约束,传统递归解法需传递上下界,但迭代法通过中序遍历可更简洁地验证。

二、迭代解法的核心实现与栈结构设计

完整迭代代码实现

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public boolean isValidBST(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode pre = null; // 记录中序遍历的前一个节点if (root == null) {return true; // 空树视为有效BST}TreeNode current = root;stack.push(current);while (!stack.isEmpty()) {current = stack.pop();if (current != null) {// 压栈顺序:右子树 → 当前节点 → 标记null → 左子树if (current.right != null) {stack.push(current.right); // 先压右子树(后续处理)}stack.push(current);        // 压入当前节点(待处理)stack.push(null);           // 压入null作为标记if (current.left != null) {stack.push(current.left); // 最后压左子树(优先处理)}} else {// 遇到null标记,处理当前节点(中序遍历的访问时机)current = stack.pop();      // 取出当前节点if (pre != null && pre.val >= current.val) {return false; // 违反递增顺序,非BST}pre = current; // 更新前一个节点}}return true; // 所有节点满足递增顺序,是BST}
}

核心数据结构设计:

  1. 栈(Stack)

    • 作用:模拟中序遍历的非递归实现,通过压栈顺序控制遍历顺序
    • 存储内容:节点与null标记(null作为访问节点的触发条件)
    • 压栈策略:右子树→当前节点→null→左子树,确保左子树优先处理
  2. pre节点

    • 作用:记录中序遍历的前一个节点值
    • 判断逻辑:当前节点值必须大于pre节点值,否则非BST

三、核心问题解析:中序遍历顺序与栈操作逻辑

1. 中序遍历的栈操作本质

传统中序遍历步骤:
  1. 遍历左子树
  2. 访问当前节点
  3. 遍历右子树
栈操作的创新压栈顺序:
if (current.right != null) {stack.push(current.right); // 步骤3:右子树后处理,先压栈
}
stack.push(current);        // 步骤2:当前节点待访问(通过null标记触发)
stack.push(null);           // 标记:触发访问当前节点的信号
if (current.left != null) {stack.push(current.left); // 步骤1:左子树先处理,最后压栈(先弹出)
}
  • 压栈顺序逆向:左子树最后压栈→最先弹出(符合栈的LIFO特性)
  • null标记作用:作为访问当前节点的分隔符,区分节点的“待处理”和“已处理”状态

2. 栈操作流程解析

压栈阶段(处理非null节点):
  1. 压右子树:右子树后处理,先压栈(后续弹出顺序靠后)
  2. 压当前节点:作为中间节点,等待左子树处理完毕后访问
  3. 压null标记:作为触发访问当前节点的信号
  4. 压左子树:左子树先处理,最后压栈(最先弹出,优先处理)
弹栈阶段(处理null标记):
  1. 遇到null标记,弹出当前节点(栈顶为null,下一个是当前节点)
  2. 比较当前节点值与pre节点值,验证递增性
  3. 更新pre节点为当前节点,继续处理右子树

四、栈操作深度模拟:以有效BST为例

示例BST结构:

    5/ \3   7/ \ / \
2  4 6  8

栈操作流程:

  1. 初始压栈:压入root(5),栈:[5]
  2. 第一次弹栈:弹出5(非null),压右子树7、当前节点5、null、左子树3,栈:[7,5,null,3]
  3. 处理左子树3:弹出3(非null),压右子树4、当前节点3、null、左子树2,栈:[7,5,null,4,3,null,2]
  4. 处理左子树2:弹出2(非null),无右子树,压当前节点2、null,无左子树,栈:[7,5,null,4,3,null,null,2]
  5. 遇到null标记:弹出null,再弹出2,pre=null,pre.val不比较,pre=2,栈:[7,5,null,4,3,null]
  6. 处理3的右子树4:弹出4(非null),无右子树,压当前节点4、null,无左子树,栈:[7,5,null,3,null,null,4]
  7. 遇到null标记:弹出null,弹出4,4>2,pre=4,栈:[7,5,null,3,null]
  8. 处理3的null标记:弹出null,弹出3,3<4,pre=3,栈:[7,5,null]
  9. 处理5的右子树7:弹出7(非null),压右子树8、当前节点7、null、左子树6,栈:[8,7,null,6,5,null]
  10. 处理左子树6:弹出6(非null),无右子树,压当前节点6、null,无左子树,栈:[8,7,null,null,6,5,null]
  11. 遇到null标记:弹出null,弹出6,6>3,pre=6,栈:[8,7,null,5,null]
  12. 处理7的右子树8:弹出8(非null),无右子树,压当前节点8、null,无左子树,栈:[7,null,5,null,null,8]
  13. 遇到null标记:弹出null,弹出8,8>6,pre=8,栈:[7,null,5,null]
  14. 处理7的null标记:弹出null,弹出7,7<8,pre=7,栈:[5,null]
  15. 处理5的null标记:弹出null,弹出5,5<7,pre=5,栈为空
  16. 遍历结束:所有节点满足递增,返回true

五、算法复杂度分析

1. 时间复杂度

  • O(n):每个节点入栈和出栈各一次,共2n次操作,线性时间复杂度

2. 空间复杂度

  • O(h):h为树的高度,栈的最大深度为树的高度
    • 平衡BST:h=logn,空间复杂度O(logn)
    • 最坏情况(退化为链表):h=n,空间复杂度O(n)

3. 与递归解法对比

方法优势劣势
迭代法避免递归栈溢出,空间更可控栈操作逻辑较复杂
递归法代码简洁,符合BST递归定义深树可能导致栈溢出

六、核心技术点总结:栈操作的三大设计原则

1. 中序遍历的逆向压栈策略

  • 左子树优先:通过“右-中-左”的压栈顺序,利用栈的LIFO特性实现“左-中-右”的访问顺序
  • null标记的分隔作用:区分节点的“待处理”和“已访问”状态,避免复杂的状态标记

2. 递增性的核心判断逻辑

  • pre节点的作用:中序遍历的前一个节点值,确保当前节点值>pre节点值
  • 严格递增约束:BST要求严格大于(非大于等于),代码中使用>=判断违反条件

3. 边界条件的处理

  • 空树处理:直接返回true,符合BST定义
  • 叶子节点处理:左右子树为空时,仅需判断自身与pre节点的关系

七、常见误区与优化建议

1. 错误理解BST定义

  • 误区:认为每个节点的左右子节点值直接与当前节点比较即可
  • 正确逻辑:需保证左子树所有节点<当前节点,右子树所有节点>当前节点(中序遍历递增)

2. 栈操作顺序错误

  • 错误压栈:先压左子树再压右子树,导致遍历顺序错误
  • 正确顺序:右-中-左的压栈顺序,确保左子树优先处理

3. 优化建议:更简洁的中序遍历实现

public boolean isValidBST(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode pre = null;while (root != null || !stack.isEmpty()) {while (root != null) { // 先压左子树到栈底stack.push(root);root = root.left;}root = stack.pop();if (pre != null && root.val <= pre.val) {return false;}pre = root;root = root.right; // 处理右子树}return true;
}
  • 优势:传统中序遍历实现,逻辑更清晰,无需null标记
  • 原理:通过循环压左子树,弹出时访问当前节点,再处理右子树

八、总结:迭代法验证BST的本质是中序遍历的巧妙实现

本算法通过栈模拟中序遍历,将BST的验证转化为递增序列的判断,核心在于:

  1. 中序遍历的性质:BST的中序遍历必为严格递增序列,抓住这一本质可简化验证逻辑
  2. 栈的逆向压栈:通过“右-中-左”的压栈顺序,利用栈的LIFO特性实现左子树优先访问
  3. null标记的创新:作为访问节点的触发信号,避免复杂的状态管理

理解这种迭代法的关键是将树的结构特性(BST的有序性)转化为线性序列的递增性判断。栈的巧妙操作使得非递归实现既保持了O(n)的效率,又避免了递归的栈溢出风险。在实际工程中,这种基于栈的迭代法常用于处理树结构的遍历问题,尤其是需要严格控制空间复杂度的场景。

相关文章:

  • 从句--02--定语从句
  • 题目 3334: 蓝桥杯2025年第十六届省赛真题-园艺
  • DL00347-基于人工智能YOLOv11的安检X光危险品刀具检测含数据集
  • 有效的字母异位符--LeetCode
  • 力扣热题100之LRU缓存机制
  • C#实现SSE通信方式的MCP Server
  • 1期临床试验中的联合i3+3设计
  • Excel快捷键大全
  • 【Spring】Spring AI 核心知识(一)
  • AI模型评估指南:准确率、召回率、F1值到底怎么用
  • TCP 三次握手,第三次握手报文丢失会发生什么?
  • lwip_bind、lwip_listen 是阻塞函数吗
  • 【LeetCode 热题 100】买卖股票的最佳时机 / 跳跃游戏 / 划分字母区间
  • 有铜半孔的设计规范与材料创新
  • C++ ——new和malloc的区别(详细)
  • JVM垃圾回收器详细介绍
  • 保姆式 网站建设wordpress全教程----包含疑难杂症
  • PHP:赋能Web开发的经典语言与未来演进
  • MySQL 中 DISTINCT 和 GROUP BY 的区别与联系
  • Linux基础与Nginx配置实战:从入门到精通
  • 网站建设主要推广方式/百度搜索推广开户
  • 网站左侧漂浮导航/互联网营销模式
  • 公司新建网站公安备案填哪个/济南seo整站优化厂家
  • 网站技术维护/网络营销渠道
  • 建设部网站水利设计资质/深圳网页设计公司
  • wordpress付费下载主题/seo排名点击软件运营