1/17, Traversal on BST


如果没说,一定要make sure是不是BST,是的话通常要利用BST的性质。

  • 左子树中所有节点小于root,右子树中所有节点大于root。
  • 最左边的节点是BST的最小值,最右边的是最大值。
  • 对BST的inorder traversal会得到一个sorted结果。

Convert BST to Greater Tree

take advantage of the features of BST, define a global sum and calculate the aggregate sum from right child to left child.

class Solution {
    int sum = 0;

    public TreeNode convertBST(TreeNode root) {
        convert(root);
        return root;
    }

    private void convert(TreeNode cur) {
        if (cur == null) return;
        convert(cur.right);
        cur.val += sum;
        sum = cur.val;
        convert(cur.left);
    }
}

Inorder Successor in Binary Search Tree

这道题分两步。首先,通过while比较当前节点与给的节点,利用BST找到给的点。然后判断给的点是否有右子树,再分两种情况。

  • 如果有右子树,就go deep left,找到这个点的右子树的最左边的点。
  • 如果没有的话,说明这个点要么是叶节点(左/右),要么有左子树。有没有左子树不重要,因为左子树已经被访问过了。只要看是左节点还是右节点,左节点就返回此节点的parent,右节点就返回它parent的parent,结果都存在successor里了。
class Solution {
    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
        if (root == null) return null;

        TreeNode successor = null;
        while (root != null && root.val != p.val) {
            if (root.val > p.val) {
                successor = root;
                root = root.left;
            } else {
                root = root.right;
            }
        }
        // if p does not exist in tree
        if (root == null) return null;

        if (root.right == null) return successor;

        root = root.right;
        while (root.left != null) {
            root = root.left;
        }
        return root;
    }
}

successor在往左走的时候,被赋成当前的节点,往右边走不会被更改。这样的话,如果是往右找到的节点没有右子树,返回的successor就是它parent的parent。如果是往左找到的节点没有右子树,返回的successor是parent。

再做这道题时,觉得很考察思路的缜密。如何一步步写branch case,优化代码的简洁性。

12/15复习注,另一种把所有节点用inorder traversal后放到array里面,再遍历找到给定点的下一个点,不是最优方法。

因为如果每次只走一边,时间复杂度是O(lgn)。而如果traversal的话,要O(n),而且需要extra space。

2/1复习,what if the node isn't in the tree?

1/19补充,F,L,G都tag过的题,fake hard。

Binary Search Tree Iterator

这题本质就是inorder traversal,跟BST没半毛钱关系。思路是把模板分解成两个function,作为class的method,并把root和stack作为class的 attribute,实现一个API可以inorder traverse tree。

public class BSTIterator {

    Deque<TreeNode> stack;
    public BSTIterator(TreeNode root) {
        stack = new LinkedList<>();
        // for (TreeNode t = root; t != null; stack.push(t), t = t.left);
        TreeNode t = root;
        while (t != null) {
            stack.push(t);
            t = t.left;
        }
    }

    /** @return whether we have a next smallest number */
    public boolean hasNext() {
        return !stack.isEmpty();
    }

    /** @return the next smallest number */
    public int next() {
        TreeNode node = stack.pop();
        if (node.right != null) {
            TreeNode temp = node.right;
            while(temp != null) {
                stack.push(temp);
                temp = temp.left;
            }
        }
        return node.val;
    }
}

Kth Smallest Element in a BST

因为对BST中序遍历能得到一个sorted结果,节点是按升序被访问的。这样的话,每次pop掉stack的一个节点,就把k减去1,直到k等于0时就是要找的点。

## pay attention, corner case: clarify what if k > num of nodes in the tree

class Solution {
    public int kthSmallest(TreeNode root, int k) {
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode cur = root;

        int i = 0;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            i++;
            TreeNode node = stack.pop();
            if (i == k) {
                return node.val;
            }
            cur = node.right;
        }
        return 0;
    }
}

套用inorder traversal的模板。

follow up: what if the BST is modified(insert / delete operations) often and you need to find the kth smallest frequently?

  • maintain a size = k 的 max heap, 一直有insert的时候好办,有delete而且删掉的node又在heap里就只好找一下inorder successor
  • 另一个应对多次查询的好办法是augmented binary tree,第一次用O(n) 的时间统计记录一下每个 node左子树节点个数, 后面的每次查询就可以 O(height) 的时间搜索了。

Find Second Largest Element in a BST

这道题要在BST里找第二大的节点,所以要先找到最大的节点(最右边的点),分两种情况考虑,一层一层剥开。

  • 如果最右边的点没有左子树,那么第二大的点是最右边点的parent。
  • 如果最右边的点有左子树,就接着找它左子树的最大的节点。
public class Solution {

    public TreeNode findSecondLargest(TreeNode root) {
        if (root == null) {
            return null;
        }

        TreeNode cur = root;
        while (cur != null) {
            if (cur.right != null && cur.right.right == null && cur.right.left == null) {
                // cur.right is largest
                return cur;
            }
            else if (cur.right == null && cur.left != null) {
                // cur is the largest
                return findLargest(cur.left);
            }
            cur = cur.right;
        }
        return cur;    // pay attention, return None if root has no children
    }

    private TreeNode findLargest(TreeNode node) {
        while (node.right) {
            node = node.right;
        }
        return node;
    }
}

迭代第一个if case的逻辑,每次得先判断是否有右节点,有的话才能利用右节点试探下面的节点。能进入第二个elif case的话,说明此时访问到了最大节点,要检查有它没有左子树。

Closest Binary Search Tree Value

Given a non-empty binary search tree and a target value, find the value in the BST that is closest to the target.

Note:

  • Given target value is a floating point.
  • You are guaranteed to have only one unique value in the BST that is closest to the target. (also needs to clarify)

12/18复习补充,利用BST,每次往左/往右走都move close to the target,时间复杂度O(lgn)。

class Solution {
    public int closestValue(TreeNode root, double target) {
        if (root == null) return -1;

        int min = root.val;
        while (root != null) {
            if (Math.abs(target - root.val) < Math.abs(target - min)) {
                min = root.val;
            }
            if (target > root.val) {
                root = root.right;
            } else if (target < root.val) {
                root = root.left;
            } else {
                break;
            }
        }
        return min;
    }
}

Verify Preorder Sequence in Binary Search Tree

note: preorder sequence, 给定array如5(123)(678),第一个元素一定为root,后面的比root小的连续序列为左子树,比root大的连续序列为右子树。左右子树可以为空,但是不能违反BST子树性质。所以如果> root的连续数组后 面又出现了< root的元素,一定是false.

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

        int rootVal = preorder[start];
        int index = start + 1;
        while (index <= end && preorder[index] < rootVal) {
            index++;
        }

        while (index <= end) {
            if (preorder[index] < rootVal) {
                return false;
            }
            index++;
        } 
        return verify(preorder, start + 1, index - 1) &&
            verify(preorder, index, end);
    }
}

Insert Node in a Binary Search Tree

插入一个节点,要保持BST的性质。用while循环来比较当前cur和给定node的值,决定往左还是往右走,记录每次的cur值,当cur为空时退出循环。然后再比较node和last cur,决定插左/插右。

走了一半的树,时间复杂度O(lgn)。

  • 迭代 ✔️

  • 递归 ✔️

    public TreeNode insertNode(TreeNode root, TreeNode node) {
        // write your code here
        if (root == null) return node;
        TreeNode cur = root, parent = null;
        while (cur != null) {
            parent = cur;
            if (cur.val > node.val) {
                cur = cur.left;
            } else {
                cur = cur.right;
            }
        }
        if (parent.val < node.val) {
            parent.right = node;
        } else {
            parent.left = node;
        }
        return root;
    }

1/26更新,recursive,实际上是保存了上一步的parent。递归出口也适用于空节点的情况。

class Solution:
    def insertNode(self, root, node):
        if not root:
            return node

        if root.val < node.val:
            root.right = self.insertNode(root.right, node)

        elif root.val > node.val:
            root.left = self.insertNode(root.left, node)

        else:
            return # node exists in the tree

        return root

1/28补充, remaining questions are all lintcode, to review in future....

Remove Node in Binary Search Tree

  • 递归 ✔️

time complexity 等于height of BST - O(lgn)

class Solution:
    def removeNode(self, root, value):
        if not root:
            return None  ## the root is empty, or the value doesn't exist in the tree 

        elif root.val > value:
            root.left = self.removeNode(root.left, value)

        elif root.val < value:
            root.right = self.removeNode(root.right, value)

        else:  ## get the node to be deleted 
            if not root.left and not root.right:
                root = None

            elif not root.left: ## pay attention use 'not'
                root = root.right

            elif not root.right:
                root = root.left

            else:
                min_node = self.findMin(root.right)
                root.val = min_node  ## copy the value of node, otherwise, copy a node may get a subtree 
                root.right = self.removeNode(root.right, min_node)

        return root  ## return a node

    def findMin(self, root):
        while root.left:
            root = root.left

        return root.val  ## pay attention, return the value of node, not node itself

思路是先通过递归找到要删的节点,再分三种情况。

case1: No children。把该node赋为None。

case2: One child。直接把child node copy过去,相当于删除了node。

case3: Two children。稍微复杂一点,先找到右子树的最小node(或者是左子树的最大node),把该node的值赋给当前要删除的node,再删掉子树里重复了的最小node。

Search Range in Binary Search Tree

在BST里找到所有值在某个范围内的节点,按升序返回。方法是遍历整棵树,比较每个点,如果在范围内就push进res。两边都走,因为如果某个点在范围内,那么它的左子树和右子树中,都可能存在满足条件的点。最后要排序,时间复杂度是O(nlgn) on average。

class Solution:
    def searchRange(self, root, k1, k2):
        if not root:
            return []

        res, nodes = [], [root]
        while nodes:
            cur = nodes.pop()
            if cur.val >= k1 and cur.val <= k2:
                res.append(cur.val)
            if cur.left:
                nodes.append(cur.left)
            if cur.right:
                nodes.append(cur.right)

        return sorted(res)

1130再做这道题才恍然大悟,之前的方法不好,没有利用inorder traversal BST的优点。既然返回结果要求ascending,也是BST inorder traversal的顺序,就能省去结尾的排序步骤。优化的时间复杂度是O(n)。

class Solution:
    def searchRange(self, root, k1, k2):
        if not root:
            return []

        res = []
        nodes = []
        cur = root

        while cur or nodes:
            while cur:
                nodes.append(cur)
                cur = cur.left

            cur = nodes.pop()
            if cur.val >= k1 and cur.val <= k2:
                res.append(cur.val)

            elif cur.val > k2:
                break

            cur = cur.right

        return res

12/15复习注,如果对返回结果要求ascending order,那还是用第二种解法好。

2/2复习注,如果检查的节点值已经大于上限,就break,没必要再继续检查后面的了。

1/1,总结BST的Traversal问题。

对于BST的Traversal/Search,一种侧重于找到某个节点,直接利用BST节点值的比较来决定搜索的方向,这样效率更高。另一种是搜索有一定的顺序要求,或者要使搜寻结果有序,这样的话,利用BST的inorder traversal效率更高。

results matching ""

    No results matching ""