96. Unique Binary Search Trees

给定 n 个 node 之后,直接看 inorder 数组【1, 2, 3, 4 .. n-1, n】

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

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

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

95. Unique Binary Search Trees II

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

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

public class Solution {
    public List<TreeNode> generateTrees(int n) {
        List<TreeNode> result = new ArrayList<>();
        if (n == 0) return result;
        result = build(1, n);
        return result;
    }
    private List<TreeNode> build(int start, int end) {
        List<TreeNode> result = new ArrayList<>();
        if (start > end) {
            result.add(null);
            return result;
        }
        for (int i = start; i <= end; ++i) {
            List<TreeNode> left = build(start, i - 1);
            List<TreeNode> right = build(i + 1, end);
            for (TreeNode l : left) {
                for (TreeNode r : right) {
                    TreeNode root = new TreeNode(i);
                    root.left = l;
                    root.right = r;
                    result.add(root);
                }
            }
        }
        return result;
    }
}

270. Closest Binary Search Tree Value

需要考虑的是状态的不连续性,所谓 “最近的点”,可能是 parent ,可能是 child,可能在左边,也可能在右边。

  • 所以一要存好 prev;

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

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

另外Integer类型可以封装int和double。

public class Solution {
    public int closestValue(TreeNode root, double target) {
        return find(root, target, null);
    }
    private int find(TreeNode root, double target, Integer prev) {
        if (root == null) return -1;
        if (prev == null) prev = root.val;

        if (Math.abs(prev - target) > Math.abs(root.val - target)) {
            prev = root.val;
        }
        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;
    }
}

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

public int closestValue(TreeNode root, double target) {
    TreeNode node = (root.val>target)?root.left:root.right;
    if (node == null) {
        return root.val;
    }
    int value = closestValue(node, target);
    return Math.abs(root.val-target) > Math.abs(value-target)?value:root.val;
}

272. Closest Binary Search Tree Value II

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

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

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

  • 做出 inorder traversal list
  • 找 target 对应的 index
  • 从 index 两边扫,寻找 k 个元素

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

public class Solution {
    public List<Integer> closestKValues(TreeNode root, double target, int k) {

        List<Integer> inorder = inorder(root);
        int index = binarySearch(inorder, target);
        List<Integer> list = new ArrayList<Integer>();

        int start = index - 1;
        int end = index;
        while(start >= 0 && end < inorder.size()){
            int num = (Math.abs(inorder.get(start) - target) < Math.abs(inorder.get(end) - target)) 
                      ? inorder.get(start--)
                      : inorder.get(end++);
            list.add(num);
            if(list.size() == k) return list;
        }
        while(start >= 0){
            list.add(inorder.get(start--));
            if(list.size() == k) return list;
        }
        while(end < inorder.size()){
            list.add(inorder.get(end++));
            if(list.size() == k) return list;
        }

        return list;
    }

    private int binarySearch(List<Integer> list, double target){
        int start = 0;
        int end = list.size() - 1;
        while(start + 1 < end){
            int mid = start + (end - start) / 2;
            if(list.get(mid) == target){
                return mid;
            } else if(target < list.get(mid)){
                end = mid;
            } else {
                start = mid;
            }
        }

        if(target < list.get(start)) return start;
        if(target > list.get(end)) return end;

        return (Math.abs(list.get(start) - target) < Math.abs(list.get(end) - target)) ? start: end;
    }

    private List<Integer> inorder(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null) return result;

        List<Integer> left = inorder(root.left);
        List<Integer> right = inorder(root.right);

        result.addAll(left);
        result.add(root.val);
        result.addAll(right);

        return result;
    }
}

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,非常简洁高效的解法,比我写的那个速度快,而且简洁明了。

public List<Integer> closestKValues(TreeNode root, double target, int k) {
    List<Integer> res = new LinkedList<Integer>();
    helper(root, target, k, res);
    return res;
}
private void helper(TreeNode root, double target, int k, List<Integer> res) {
    if (root == null) {
        return;
    }
    helper(root.left,target,k,res);
    if (res.size()< k) {
        res.add(root.val);
    } else {
        if (Math.abs(res.get(0)-target) > Math.abs(root.val-target)) {
            res.remove(0);
            res.add(root.val);
        } else {
            return;
        }
    }
    helper(root.right,target,k,res);
}

255. Verify Preorder Sequence in Binary Search Tree

相关题:331. Verify Preorder Serialization of a Binary Tree

这题的题意需要澄清下,正确意思是,给定一个 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 要考虑。length <= 2时,因为只有两个点,既可以当做左子树为空也可以当做右子树为空,只要最后返回出valid BST就行。

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

public class Solution {
    public boolean verifyPreorder(int[] preorder) {
        return helper(0, preorder.length - 1, preorder);
    }
    private boolean helper(int start, int end, int[] preorder) {
        if (end - start <= 1) return true;

        int rightStart = start + 1;

        for (int i = start + 1; i <= end; ++i) {
            if (preorder[i] < preorder[start]) {
                rightStart++;
            } else break;
        }
        for (int i = rightStart; i <= end; ++i) {
            if (preorder[i] < preorder[start]) return false;
        }
        return helper(start + 1, rightStart - 1, preorder) && helper(rightStart, end, preorder);
    }
}

99. Recover Binary Search Tree

这道题我先不说Morris遍历的做法,先分析一下这道题。

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

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

因为 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.

递归写法

我觉得这种写法其实已经足够简洁了,但如果二叉树深度很深,不断递归会造成栈溢出。还是不如Morris的写法。

public void recoverTree(TreeNode root) {
    //use inorder traversal to detect incorrect node

    inOrder(root);

    int temp = first.val;
    first.val = second.val;
    second.val = temp;
}

TreeNode prev = null;
TreeNode first = null;
TreeNode second = null;

public void inOrder(TreeNode root){
    if(root == null) return;
    //search left tree
    inOrder(root.left);

    //in inorder traversal of BST, prev should always have smaller value than current value
    if(prev != null && prev.val >= root.val){
        //incorrect smaller node is always found as prev node
        if(first == null) first = prev;
      //incorrect larger node is always found as curr(root) node
        second = root;
    }

    //update prev node
    prev = root;

    //search right tree
    inOrder(root.right);
}

results matching ""

    No results matching ""