您现在的位置是:首页 >技术教程 >【忍者算法】超市货架里的秘密:用生活常识理解二叉搜索树验证|LeetCode 98 验证二叉搜索树网站首页技术教程

【忍者算法】超市货架里的秘密:用生活常识理解二叉搜索树验证|LeetCode 98 验证二叉搜索树

忍者算法_ 2025-05-14 00:01:04
简介【忍者算法】超市货架里的秘密:用生活常识理解二叉搜索树验证|LeetCode 98 验证二叉搜索树

超市货架里的秘密:用生活常识理解二叉搜索树验证

生活小剧场:超市理货员的烦恼

假设你是超市的新员工,负责整理饮料货架。主管给你两条规则:

  1. 每层货架左边只能放低价商品,右边放高价商品
  2. 每个商品的价格标签必须清晰可见

但有个狡猾的同事把货架搞得一团糟:

    可乐 ¥5
   /     
雪碧 ¥4  芬达 ¥6 
     
    脉动 ¥5.5 ← 藏在可乐左边却比可乐便宜!

你会发现虽然直接看可乐的左右没问题,但隐藏的脉动才是问题所在。这就是验证二叉搜索树的现实写照!

问题解析:到底在验证什么?

我们需要确认这棵树满足:

  1. 每个节点左边的所有子孙(不光是直接左孩子)都比它小
  2. 每个节点右边的所有子孙(不光是直接右孩子)都比它大

就像检查整个货架区域,不能只看相邻商品,要确保整个左半区都符合规则。

常见错误:只检查眼前货物

新手最容易写成这样的错误代码:

boolean check(TreeNode node) {
    if (node == null) return true;
    if (node.left != null && node.left.val >= node.val) return false;
    if (node.right != null && node.right.val <= node.val) return false;
    return check(node.left) && check(node.right);
}

这就像只检查相邻商品:

    5
   / 
  1   6
     / 
    4   7 ← 4虽然小于6,但应该大于5!

明明4出现在5的右侧区域却比5小,这种深层问题会被漏掉。

正确方法一:传递价格标签法

核心思想

想象每个商品都带有两个隐形标签:

  • 最低允许价格(不能比这个低)
  • 最高允许价格(不能比这个高)

比如可乐(5元)的标签是:

  • 左区商品:最高价 ≤5元
  • 右区商品:最低价 ≥5元

具体步骤

  1. 给根节点设置初始范围(无限小到无限大)
  2. 检查当前节点是否在允许范围内
  3. 给左孩子设置新范围(继承父节点下限,上限设为当前值)
  4. 给右孩子设置新范围(继承父节点上限,下限设为当前值)

代码示例(Java)

public boolean isValidBST(TreeNode root) {
    return check(root, Long.MIN_VALUE, Long.MAX_VALUE);
}

boolean check(TreeNode node, long minAllow, long maxAllow) {
    if (node == null) return true;
    
    // 当前商品价格是否在允许区间内
    if (node.val <= minAllow || node.val >= maxAllow) return false;
    
    // 检查左区(价格必须 < 当前价)
    boolean leftValid = check(node.left, minAllow, node.val);
    
    // 检查右区(价格必须 > 当前价)
    boolean rightValid = check(node.right, node.val, maxAllow);
    
    return leftValid && rightValid;
}

关键细节

  • 使用Long类型:避免数字边界问题(比如节点值是Integer.MIN_VALUE)
  • 前开后闭区间:确保严格大于/小于

正确方法二:商品价格单检查法

核心思想

如果这是合法的BST,那么按照"左→中→右"顺序记录价格时,价格单应该是严格递增的。就像整理好的货架,从左到右价格应该一直上涨。

操作步骤

  1. 准备一个记事本记录上一个价格
  2. 按照左→中→右顺序遍历货架
  3. 每次记录新价格时,检查是否比上一个高

代码示例(Python)

def isValidBST(root):
    self.last_val = -float('inf')  # 初始设为极小值
    
    def traverse(node):
        if not node: return True
        
        # 先检查左边货架
        if not traverse(node.left): return False
        
        # 检查当前价格
        if node.val <= self.last_val:
            return False
        self.last_val = node.val  # 更新记录本
        
        # 最后检查右边货架
        return traverse(node.right)
    
    return traverse(root)

复杂度分析

两种方法都像检查整个货架:

  • 时间:每个商品检查一次 → O(n)
  • 空间:最差情况要记住整条货架的位置 → O(n)(比如货架摆成一条直线)

思维升级:为什么这两种方法有效?

  1. 边界传递法像给每个区域贴警示标签。

  2. 中序检查法像拿着清单逐项核对。

常见疑问解答

Q:为什么要用Long类型?用Integer不行吗?
A:就像货架可能有"¥-2147483648"这样的超低价商品,用Integer会导致边界判断错误。Long类型就像扩展了价格标签的范围。

Q:空树(没有商品)算合法BST吗?
A:是的!就像空货架当然符合规则。

Q:如果有重复价格怎么办?
A:BST要求严格小于/大于,重复价格就像两件同价商品放在同一层,这是不允许的。


关注「忍者算法」获取更多秘籍
在公众号后台回复:

  • 【刷题清单】,获取从0开始的完整刷题路线图,以及这些题目的详细题解,覆盖了绝大部分常见面试题。只要你把这些题真正掌握了,80%的算法面试都能遇到相似题目!
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。