zoukankan      html  css  js  c++  java
  • LeetCode-关于二叉搜索树的算法总结

    • 二叉搜索树的升序输出或者升序查找第k个元素之类的问题可以依靠中序遍历,BST 相关的问题,要么利用 BST 左小右大的特性提升算法效率,要么利用中序遍历的特性满足题目的要求

    • 如果当前节点会对下面的子节点有整体影响,可以通过辅助函数增长参数列表,借助参数传递信息。

    • 在二叉树递归框架之上,扩展出一套 BST 代码框架:

      void BST(TreeNode root, int target) {
          if (root.val == target)
           // 找到目标,做点什么
          if (root.val < target)
              BST(root.right, target);
          if (root.val > target)
              BST(root.left, target);
      }
      
    • 在此基础上对二叉搜索树进行增删改查以及验证

      验证二叉搜索树的合法性

      在验证是否是二叉搜索树的时候,不应该简单比较左子节点的值<root.val<右节点的值,而是要满足左子树的最大值<root.val< 右子树的最小值,所以要利用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点,限定左子树的最大值是 root.val,右子树的最小值是 root.val,这也是二叉树算法的一个小技巧吧

      以下是正确代码:

      boolean isValidBST(TreeNode root) {
          return isValidBST(root, null, null);
      }
      
      /* 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val */
      boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) {
          // base case
          if (root == null) return true;
          // 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST
          if (min != null && root.val <= min.val) return false;
          if (max != null && root.val >= max.val) return false;
          // 限定左子树的最大值是 root.val,右子树的最小值是 root.val
          return isValidBST(root.left, min, root)
                  && isValidBST(root.right, root, max);
      }
      

    在增删改的时候要结合二叉搜索树的特性,利用二分搜索来减少查找时间,在删的时候要格外注意,

    删除二叉搜索树的某个节点

    找到目标节点了,比方说是节点 A,如何删除这个节点,这是难点。因为删除节点的同时不能破坏 BST 的性质。有三种情况,用图片来说明。

    情况 1A 恰好是末端节点,两个子节点都为空,那么它可以当场去世了。

    image

    if (root.left == null && root.right == null)
            return null;
    

    情况 2A 只有一个非空子节点,那么它要让这个孩子接替自己的位置。

    image

    // 排除了情况 1 之后
    if (root.left == null) return root.right;
            if (root.right == null) return root.left;
    

    情况 3A 有两个子节点,麻烦了,为了不破坏 BST 的性质,A 必须找到左子树中最大的那个节点,或者右子树中最小的那个节点来接替自己。我们以第二种方式讲解。

    image

    if (root.left != null && root.right != null) {
            // 找到右子树的最小节点
            TreeNode minNode = getMin(root.right);
            // 把 root 改成 minNode
            root.val = minNode.val;
            // 转而去删除 minNode
            root.right = deleteNode(root.right, minNode.val);
     }
    

    三种情况分析完毕,填入框架,简化一下代码:

    TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;
        if (root.val == key) {
            // 这两个 if 把情况 1 和 2 都正确处理了
            if (root.left == null) return root.right;
            if (root.right == null) return root.left;
            // 处理情况 3
            TreeNode minNode = getMin(root.right);
            root.val = minNode.val;
            root.right = deleteNode(root.right, minNode.val);
        } else if (root.val > key) {
            root.left = deleteNode(root.left, key);
        } else if (root.val < key) {
            root.right = deleteNode(root.right, key);
        }
        return root;
    }
    
    TreeNode getMin(TreeNode node) {
        // BST 最左边的就是最小的
        while (node.left != null) node = node.left;
        return node;
    }
    

    不同的二叉搜索树

    第 96 题「不同的二叉搜索树」,给你输入一个正整数 n,请你计算,存储 {1,2,3...,n} 这些值共有有多少种不同的 BST 结构。

    二叉树算法的关键就在于明确根节点需要做什么,其实 BST 作为一种特殊的二叉树,核心思路也是一样的。

    举个例子,比如给算法输入 n = 5,也就是说用 {1,2,3,4,5} 这些数字去构造 BST。

    每个数字都可以作为根节点。

    固定 3 作为根节点,这个前提下能有几种不同的 BST 呢?

    根据 BST 的特性,根节点的左子树都比根节点的值小,右子树的值都比根节点的值大。

    所以如果固定 3 作为根节点,左子树节点就是 {1,2} 的组合,右子树就是 {4,5} 的组合。

    左子树的组合数和右子树的组合数乘积就是 3 作为根节点时的 BST 个数。

    image

    怎么让算法进行计算呢?

    只需要递归就行了,我们可以写这样一个函数:

    // 定义:闭区间 [lo, hi] 的数字能组成 count(lo, hi) 种 BST
    int count(int lo, int hi);
    

    根据这个函数的定义,结合刚才的分析,可以写出代码:

    /* 主函数 */
    int numTrees(int n) {
        // 计算闭区间 [1, n] 组成的 BST 个数
        return count(1, n);
    }
    
    /* 计算闭区间 [lo, hi] 组成的 BST 个数 */
    int count(int lo, int hi) {
        // base case
        if (lo > hi) return 1;
    int res = 0;
    for (int i = lo; i <= hi; i++) {
        // i 的值作为根节点 root
        int left = count(lo, i - 1);
        int right = count(i + 1, hi);
        // 左右子树的组合数乘积是 BST 的总数
        res += left * right;
    }
    
    return res;
    }
    

    base case,显然当 lo > hi 闭区间 [lo, hi] 肯定是个空区间,也就对应着空节点 null,虽然是空节点,但是也是一种情况,所以要返回 1 而不能返回 0。

    但是时间复杂度非常高,肯定存在重叠子问题。

    消除重叠子问题的方法,无非就是加一个备忘录:

    // 备忘录
    int[][] memo;
    
    int numTrees(int n) {
        // 备忘录的值初始化为 0
        memo = new int[n + 1][n + 1];
        return count(1, n);
    }
    
    int count(int lo, int hi) {
        if (lo > hi) return 1;
        // 查备忘录
        if (memo[lo][hi] != 0) {
            return memo[lo][hi];
        }
    int res = 0;
    for (int mid = lo; mid <= hi; mid++) {
        int left = count(lo, mid - 1);
        int right = count(mid + 1, hi);
        res += left * right;
    }
    // 将结果存入备忘录
    memo[lo][hi] = res;
    
    return res;
    }
    
    第 95 题「不同的二叉搜索树 II」,让你构建所有 BST

    明白了上道题构造合法 BST 的方法,这道题的思路也是一样的

    1、穷举 root 节点的所有可能。

    2、递归构造出左右子树的所有合法 BST。

    3、给 root 节点穷举所有左右子树的组合。

    代码如下:

    /* 主函数 */
    public List<TreeNode> generateTrees(int n) {
        if (n == 0) return new LinkedList<>();
        // 构造闭区间 [1, n] 组成的 BST 
        return build(1, n);
    }
    
    /* 构造闭区间 [lo, hi] 组成的 BST */
    List<TreeNode> build(int lo, int hi) {
        List<TreeNode> res = new LinkedList<>();
        // base case
        if (lo > hi) {
            res.add(null);
            return res;
        }
    // 1、穷举 root 节点的所有可能。
    for (int i = lo; i <= hi; i++) {
        // 2、递归构造出左右子树的所有合法 BST。
        List<TreeNode> leftTree = build(lo, i - 1);
        List<TreeNode> rightTree = build(i + 1, hi);
        // 3、给 root 节点穷举所有左右子树的组合。
        for (TreeNode left : leftTree) {
            for (TreeNode right : rightTree) {
                // i 作为根节点 root 的值
                TreeNode root = new TreeNode(i);
                root.left = left;
                root.right = right;
                res.add(root);
            }
        }
    }
    
    return res;
        
    }
    

    总结出了如下几个技巧:

    1、如果当前节点会对下面的子节点有整体影响,可以通过辅助函数增长参数列表,借助参数传递信息。

    2、在二叉树递归框架之上,扩展出一套 BST 代码框架:

    void BST(TreeNode root, int target) {
        if (root.val == target)
        // 找到目标,做点什么
        if (root.val < target)
            BST(root.right, target);
        if (root.val > target)
            BST(root.left, target);
    }
    
  • 相关阅读:
    解决错误APB AP transaction error, DAP status f0000021
    快速实现Flash自动烧写功能
    [分享] Zynq-7000 XIP 2018.3,在QSPI Flash中运行程序
    区块链入门到实战(2)之区块链 – 发展历史
    区块链入门到实战(1)之区块链 – 介绍
    Python爬虫小白入门(十四)Python 爬虫 – 提取数据到Pandas DataFrame
    Python爬虫小白入门(十三)Python 爬虫 – 使用CSS选择器
    Python爬虫小白入门(十二)Python 爬虫 – 根据id与class查找标签
    Python爬虫小白入门(十一)Python 爬虫 – 根据类型查找标签
    Python爬虫小白入门(十)Python 爬虫 – BeautifulSoup分析页面
  • 原文地址:https://www.cnblogs.com/RealGang/p/14872683.html
Copyright © 2011-2022 走看看