LeetCode 96.不同的二叉搜索树

257 阅读2分钟

「这是我参与2022首次更文挑战的第32天,活动详情查看:2022首次更文挑战」。

题目:给定一个正整数n, 求由1-nn个节点组成的不同的二叉搜索树有多少种,返回符合题意的二叉搜索树的数量。

解题思路

所谓的二叉搜索树,是指如果一棵树的左右子树不空,则有左子树上所有节点都小于根节点,右子树上的所有树节点的值都大于根节点,并且该树上的所有子树都满足该条件。根据本题题意,一棵树由n个节点组成, 并且这些节点的内容为1-n,可以分开进行考虑,因为每个节点都可以做根节点,那么假设以i节点为根节点的不同的二叉搜索树有f(i)f(i),最终n个节点的不同二叉搜索树总量为G(n)G(n),则可得:

G(n)=f(1)+f(2)+f(3)+...+f(n)G(n) = f(1) + f(2) + f(3) + ... + f(n)

而对于节点i,如果节点i为根节点,则其所有的左子树节点为[1,i-1], 其右子树所有节点为[i+1, n],因此左子树节点总数为i-1个,右子树节点总数为n-i个。而分开的左子树和右子树的节点也可以单独进行如上分析, 因此以节点i为根节点的不同的二叉搜索树的总量为:

f(i)=G(i1)G(ni)f(i) = G(i-1)*G(n-i)

则最终总量G(n)G(n)为:

G(n)=i=1nG(i1)G(ni)G(n) = \sum_{i=1}^{n}G(i-1)*G(n-i)

在代码实现中, G(n)G(n)可以直接看作是一个动态规划数组,最终求得的数组最后一个位置的值即为最终的结果。代码如下:

public int numTrees(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i] += dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }

上述代码的时间复杂度为O(n2)O(n^2), 空间复杂度为O(n)O(n)。上述代码需要注意的是两层for循环中内层循环的j起始点,通过dp[2]模拟一下很容易直到j是从1开始的。实际上,上述总量公式即为卡特兰数公式,可通过求卡特兰数得到最终的答案。

验证二叉搜索树

再来看看LeetCode 98的验证二叉搜索树, 给定一个二叉树的根节点root,要求判断该树是不是一个有效的二叉搜索树。

思路

根据二叉搜索树的定义,树的节点大小关系为左<中<右。符合中序遍历的结果,只需将二叉树进行中序遍历,之后判断每次的值是否为升序即可,代码如下:

public boolean isValidBST(TreeNode root) {
        Deque<TreeNode> stack = new ArrayDeque<>();
        double value = -Double.MAX_VALUE;
        while(!stack.isEmpty()||root!=null){
            if(root!=null){
                stack.push(root);
                root = root.left;
            }else {
                root = stack.pop();
                if(value<root.val){
                    value = root.val;
                }else {
                    return false;
                }
                root = root.right;
            }
        }
        return true;
    }

此处需要注意的是在设置value值的时候,需要设定为-Double.MAX_VALUE而不能设置为Integer.MIN_VALUE,这样测试用例不会出现问题。