子树组合,BST query

Java 带泛型的 Collections 确实是可以放 null 的, 比如 Queue, List...

这题考察的是 Binary Tree 和 BST 的结构,比较容易想到的思路是画几个 base case 出来然后开始往上加新的 node,不过容易陷进去,开始去抠 detail 去开始想如何添加新点而不违反 BST 性质。

上面那个思路只对了一半,因为 F(n) 是和之前的解 F(n - a) 有联系的,但是思考方向错了。

正确的思路是,给定 n 个 node 之后,直接看 inorder 数组【1, 2, 3, 4 .. n-1, n】

那么选任意位置的元素为 root,都可以建出来一个 valid BST,左子树为 index 左边的 subarray,右子树为 index 右边的 subarray.

于是这就变成了一个利用递归结构的“组合”问题了,解的数量左右相乘。inorder 是 BST 的灵魂啊。

下面是第一次写 AC 的代码,有点粗糙。

public class Solution {
    public int numTrees(int n) {
        if(n <= 2) return n;

        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;

        for(int i = 3; i <= n; i++){
            for(int j = 0; j <= i - 1; j++){
                int leftCount = j;
                int rightCount = i - 1 - j;

                dp[i] += dp[leftCount] * dp[rightCount];
            }
        }


        return dp[n];
    }
}

顺着上题的思路,这题也很好做。让我比较惊讶的是改好了拼写之后居然一次提交直接 AC....

参考了下论坛,同一个思路,比较简洁的写法是

Java 带泛型的 Collections 确实是可以放 null 的, 比如 Queue, List...

在这题里最两边的情况下,list 里直接加个 null 作为 node 用就可以让代码变得非常简洁。

比较简单的问题,唯一需要考虑的是状态的不连续性;最接近的点可能是 BST 一直往下走的 node,也可能是前面某个 node.

用区间的思想去理解的话,每个 node 都有自己的 valid 区间,区间与区间是重合的。对于给定 target,我们先要找到 target 是什么,然后决定 target 到底靠近重合区间的左边还是右边。

下面是论坛上的写法,思想一致,更取巧了一些。

这道题是在二叉树上做 range query.

二叉树上做 range query 普遍要依靠 getPrev() 和 getNext() 函数,或者利用 Parent 指针做 traversal.

但是 BST 不一样,如上题所说, inorder 是 BST 的灵魂。因此这道题可以分为三部分;

  • 做出 inorder traversal list

  • 找 target 对应的 index

  • 从 index 两边扫,寻找 k 个元素

只在主函数的起始 index 上出了一个小 bug,基本算是一次 AC.

时间复杂度 O(n) + O(log n) + O(k) = O(n)

LeetCode 论坛上还有一些其他的写法,也很有意思。这个写法是 O(n log n) + O(k log n),用自定义的 min heap.

Java 內建的 LinkedList 库是双向链表,implements Deque.

这个解法我很喜欢,类似于 sliding window maximum 一样,维护一个大小为 k 的 sliding window,在树上做 inorder traversal,每当 window size == k 但是新 node 值比 head 的值更接近于 target 的时候,就给替换掉。当后来发现新来的 node 不比 head 更接近于 target 的时候,就可以返回结果了,而且因为 LinkedList 继承了 List,连类型转换都不需要。

时间复杂度 O(n) ,还带 early termination,非常简洁高效的解法,比我写的那个速度快,而且简洁明了。

Last updated

Was this helpful?