BST

  • 递归时,要考虑状态可能的不连续性。

  • 取左右子树的 min/max 之后加上当前节点是最常见的递归结构,保证了 path 自顶向下的连续。

  • BST 做 in order traversal 得到的结果是 sorted.

  • BST 中 root 的左子树所有点小于 root; 右子树所有点大于 root.

  • 一个正确的 BST 中每一个点都有其合理的取值闭区间,为【左子树 max , 右子树 min】,最左右两端的点为一端开放区间。

  • 维护大小为 k 的 MaxHeap:

PriorityQueue<Integer> queue = new PriorityQueue<>(k, Collections.reverseOrder());

Tree 类问题中,递归转迭代的常用手法,就是利用尾递归。

所谓 “最近的点”,可能是 parent ,可能是 child,可能在左边,也可能在右边。

  • 所以一要存好 prev;

  • 二要两边都探,不能沿着一边硬走。

public class Solution {
    public int closestValue(TreeNode root, double target) {
        return find(root, target, null);
    }

    public int find(TreeNode root, double target, Integer prev){
        if(root == null) return -1; // Error
        if(prev == null) prev = root.val;

        prev = (Math.abs(root.val - target) < Math.abs(prev - target)) ? root.val: prev;

        if(root.left != null && target < root.val){
            return find(root.left, target, prev);
        }
        if(root.right != null && target > root.val){
            return find(root.right, target, prev);
        }

        return prev;
    }
}

这题论坛里还有更简洁直接的迭代写法,其实就是尾递归变迭代;

一开始写挂了两次,因为忘了另外的条件,就是右子树里面的最小值一定要大于 root,并且左子树里面的最大值一定要小于 root 才行。

  • 多研究下 subtree 的结构和性质,目前对这个理解还不够。

  • 递归时,要考虑状态可能的不连续性,这题和 Binary Tree Maximum Path Sum 很像。

写的比较粗糙的第一版,用 Tuple 存非连续子树信息;

顺着这个思路,更简洁清晰的写法是这样。

  • 这个写法是在 BST 中定义 “区间”,即对于一个正在考虑的 root,检查值是否处于合理区间内,也即 【左子树max ,右子树min】之间。

  • 利用 Integer 是 object 的性质,用 null reference 代表开区间,避免 node 值为 Integer.MIN/MAX 的情况。

同样的,这题用 inorder traversal 也可以做,也不需要设全局变量。

这题考察的就是 BST 的性质: in order = sorted.

另一种应对多次查询的好办法是 augmented binary tree; 第一次用 O(n) 的时间统计记录一下每个 node 左子树节点个数,后面的每次查询就可以 O(height) 的时间搜索了。

Follow up:

What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine?

维护一个大小为 k 的 max heap,一直有 insert 的时候好办;有 delete 而且删掉的 node 又在 heap 里就只好找一下 in order successor 了。

简单写法是 naive 的 in order traversal. 只要是 binary tree 都可以用。

  • 不过这题还可以进一步利用 BST 的性质,不依赖 stack,只依靠值去模拟 inorder 的过程。

更像 in order 的写法是这样:

利用 BST inorder = sorted 的性质,一道很有意思的递归题。

在 BST 里多用区间的思想思考问题。

  • 只给定一个 preorder 顺序是无法生成唯一 binary tree 的,也可以生成多个 valid BST.

所以这题的题意需要澄清下,正确意思是,给定一个 preorder sequence,只要这个 sequence 可以生成一个 valid BST,都返回 true.

这样我们的判断条件变成了这样,给定 array 如 5(123)(678),第一个元素一定为 root,后面的比 root 小的连续序列为左子树,比 root 大的连续序列为右子树。

左右子树可以为空,但是不能违反 BST 子树性质。所以如果 > root 的连续数组后面又出现了 < root 的元素,一定是 false.

这题的写法上有点像 quicksort,要注意 index.

  • 为了省心起见,这类 subarray 的 divide & conquer 最好把 length <= 2 的直接返回了,不然会多出许多 corner case 要考虑。

  • O(nlogn) time and O(1) space

  • Java 里一切都是 pass by value,当传入的是 object reference 的时候,实际传入的就是 object 的内存地址。

  • 因此,带泛型的各种数据结构,Stack, List 等,即使里面放的都是 TreeNode 并且进行了各种 add/remove/pop 操作,对这些 object 的修改也会反映到原来的 Tree 上。

Hard 题,因为得 O(1) space. 这意味着做个 in order 存在 array 里面再扫的做法行不通,连 stack 也不能用了。。

先假设下我们有 inorder array,如何找那对 swapped pair ?

在中间 1234567 -> 1264537

在两端 1234567 -> 7234561

右端 1234567 -> 1237564

左端 1234567 -> 5234167

  • 从左往右找递增序列里面第一个变小的,prev 为 swapped;

  • 从右往左找递减序列里面第一个变大的,prev 为 swapped.

  • 找错误元素可以简化成一次遍历,第一次找到违章元素,prev 是 swapped; 然后继续扫,第二次看到违章元素,cur 是 swapped.

所以顺着这个思路的第一种暴力写法是建一个 arraylist 存 inorder traversal,然后扫两遍去找到底应该 swap 哪两个。然而 TLE 了,test case 故意坑人,教育我们不要一言不合就遍历。。。

于是改良版的写法是,先搞一个从左往右的 inorder,然后找第一个违章元素 on the fly.

然后做一个从右往左的 inorder,找第一个违章元素 on the fly.

这样不需要遍历整个 list,可以利用 early termination.

于是。。。卧槽,居然 AC 了?

遍历和迭代更简洁的写法在论坛这个帖子里写的非常好。

找错误元素可以简化成一次遍历,第一次找到违章元素,prev 是 swapped; 然后继续扫,第二次看到违章元素,cur 是 swapped.

递归版:

迭代版:

Last updated

Was this helpful?