Leetcode 75: Recover Binary Search Tree


Yu's Coding Garden : leetcode Question 75: Recover Binary Search Tree
Two elements of a binary search tree (BST) are swapped by mistake.
Recover the tree without changing its structure.
Example 1:
Input: [1,3,null,null,2]

   1
  /
 3
  \
   2

Output: [3,1,null,null,2]

   3
  /
 1
  \
   2
Example 2:
Input: [3,1,4,null,null,2]

  3
 / \
1   4
   /
  2

Output: [2,1,4,null,null,3]

  2
 / \
1   4
   /
  3
Follow up:
  • A solution using O(n) space is pretty straight forward.
  • Could you devise a constant space solution?

So the easiest way is inorder traverse the BST and find the element pair (two elements) which are not consistent with the definition of BST. In order to get the order, a queue is needed, which is O(n).

Now how to do this procedure in O(1)?
What we need is actually two pointers, which point to 2 tree nodes where is incorrect. Therefore, we only need to store these two pointers, and, we also need another pointer to store the previous element, in order to compare if the current element is valid or not.

The time complexity is O(n). But the space complexity is not constant, since we use recursive function.
https://leetcode.com/problems/recover-binary-search-tree/discuss/32535/No-Fancy-Algorithm-just-Simple-and-Powerful-In-Order-Traversal
This question appeared difficult to me but it is really just a simple in-order traversal! I got really frustrated when other people are showing off Morris Traversal which is totally not necessary here.
Let's start by writing the in order traversal:
private void traverse (TreeNode root) {
   if (root == null)
      return;
   traverse(root.left);
   // Do some business
   traverse(root.right);
}
So when we need to print the node values in order, we insert System.out.println(root.val) in the place of "Do some business".
What is the business we are doing here?
We need to find the first and second elements that are not in order right?
How do we find these two elements? For example, we have the following tree that is printed as in order traversal:
6, 3, 4, 5, 2
We compare each node with its next one and we can find out that 6 is the first element to swap because 6 > 3 and 2 is the second element to swap because 2 < 5.
Really, what we are comparing is the current node and its previous node in the "in order traversal".


Let us define three variables, firstElement, secondElement, and prevElement. Now we just need to build the "do some business" logic as finding the two elements. See the code below:


    TreeNode firstElement = null;
    TreeNode secondElement = null;
    // The reason for this initialization is to avoid null pointer exception in the first comparison when prevElement has not been initialized
    TreeNode prevElement = new TreeNode(Integer.MIN_VALUE);
    
    public void recoverTree(TreeNode root) {
        
        // In order traversal to find the two elements
        traverse(root);
        
        // Swap the values of the two nodes
        int temp = firstElement.val;
        firstElement.val = secondElement.val;
        secondElement.val = temp;
    }
    
    private void traverse(TreeNode root) {
        
        if (root == null)
            return;
            
        traverse(root.left);
        
        // Start of "do some business", 
        // If first element has not been found, assign it to prevElement (refer to 6 in the example above)
        if (firstElement == null && prevElement.val >= root.val) {
            firstElement = prevElement;
        }
    
        // If first element is found, assign the second element to the root (refer to 2 in the example above)
        if (firstElement != null && prevElement.val >= root.val) {
            secondElement = root;
        }        
        prevElement = root;

        // End of "do some business"

        traverse(root.right);
}
For anyone who is worried about the case when root.val==Integer.MIN_VALUE, here is a slightly updated version setting pre=null
    private TreeNode first;
    private TreeNode second;
    private TreeNode pre;
    public void recoverTree(TreeNode root) {
        if(root==null) return;
        first = null;
        second = null;
        pre = null;
        inorder(root);
        int temp = first.val;
        first.val = second.val;
        second.val = temp;
    }
    
    private void inorder(TreeNode root){
        if(root==null) return;
        inorder(root.left);
        
        if(first==null && (pre==null ||pre.val>=root.val)){
            first = pre;
        }
        if(first!=null && pre.val>=root.val){
            second = root;
        }
        pre = root;
        inorder(root.right);
    }
http://buttercola.blogspot.com/2014/08/leetcode-recover-binary-search-tree.html
http://www.jiuzhang.com/solutions/recover-binary-search-tree/
If a binary tree is a BST, its inorder traversal should be in ascending order. We may use this property to solve this problem. Consider the following case:
For the inorder traversal list 123456, if 2 and 6 are swapped, the list becomes
163452. So when we scan the list, our goal is to mark the two swapped numbers and swap them back. So we can use two pointers, p and q. We compare the current value with the previous one, so when we see the first descending value, which is 3, the p pointer points to the previous node before 3, which is 6. When the next time we see the descending order again, which is 2, we use q pointer points to 2. Then we can swap the two numbers. 

Do we consider all the case? Actually there is still a case we did not consider. For the two numbers in adjacent, e.g. 123456, where 34 are swapped, so now it is 124356. The first time it encounters a descending order is at 3, where we point p to 4, the one before 3, then there is no other descending later. So where is q? So we should point q to this current position. 

    TreeNode p,q;
    TreeNode prev = null;
    public void recoverTree(TreeNode root) {
         
        inOrderTraversal(root);
         
        int temp = p.val;
        p.val = q.val;
        q.val = temp;
    }
     
    private void inOrderTraversal(TreeNode root) {
        if (root == null) return;
        inOrderTraversal(root.left);
         
        if (prev != null && root.val < prev.val) {
            if (p == null) {
                p = prev;
                q = root;
            } else {
                q = root;
            }
        }
        prev = root;
        inOrderTraversal(root.right);
    }
https://discuss.leetcode.com/topic/3988/no-fancy-algorithm-just-simple-and-powerful-in-order-traversal
    TreeNode firstElement = null;
    TreeNode secondElement = null;
    // The reason for this initialization is to avoid null pointer exception in the first comparison when prevElement has not been initialized
    TreeNode prevElement = new TreeNode(Integer.MIN_VALUE);
    
    public void recoverTree(TreeNode root) {
        
        // In order traversal to find the two elements
        traverse(root);
        
        // Swap the values of the two nodes
        int temp = firstElement.val;
        firstElement.val = secondElement.val;
        secondElement.val = temp;
    }
    
    private void traverse(TreeNode root) {
        
        if (root == null)
            return;
            
        traverse(root.left);
        
        // Start of "do some business", 
        // If first element has not been found, assign it to prevElement (refer to 6 in the example above)
        if (firstElement == null && prevElement.val >= root.val) {
            firstElement = prevElement;
        }
    
        // If first element is found, assign the second element to the root (refer to 2 in the example above)
        if (firstElement != null && prevElement.val >= root.val) {
            secondElement = root;
        }        
        prevElement = root;

        // End of "do some business"

        traverse(root.right);
}
http://www.cnblogs.com/yuzhangcmu/p/4208319.html
例如1,4,3,2,5,6
1.当我们读到4的时候,发现是正序的,不做处理
2.但是遇到3时,发现逆序,将4存为第一个错误节点,3存为第二个错误节点
3.继续往后,发现3,2又是逆序了,那么将第2个错误节点更新为2
如果是这样的序列:1,4,3,5,6同上,得到逆序的两个节点为4和3。
========================================
这里我们补充一下,为什么要替换第二个节点而不是第一个节点:
e.g. The correct BST is below:
【LeetCode】Recover <wbr>Binary <wbr>Search <wbr>Tree
The inorder traversal is :  1 3 4 6 7 8 10 13 14

Find the place which the order is wrong.
        Wrong order: 1 3 8 6 7 4 10 13 14
        FIND:                    8 6
        Then we find:             7 4
        8, 6 是错误的序列, 但是,7,4也是错误的序列。
        因为8,6前面的序列是正确的,所以8,6一定是后面的序列交换来的。
        而后面的是比较大的数字,也就是说8一定是被交换过来的。而7,4
        中也应该是小的数字4是前面交换过来的。

        用反证法来证明:
        假设:6是后面交换过来的
        推论: 那么8比6还大,那么8应该也是后面交换来的,
        这样起码有3个错误的数字了
        而题目是2个错误的数字,得证,只应该是8是交换过来的。
结论就是:我们需要交换的是:8, 4.

    TreeNode *first;
    TreeNode *second;
    TreeNode *pre;
     
    void inOrder(TreeNode *root){
        if (root==NULL){return;}
        else{
            inOrder(root->left);
            if (pre == NULL){pre = root;}
            else {
                if (pre->val > root->val){
                    if (first==NULL) {first = pre;}
                    second = root;
                }
                pre = root;
            }
            inOrder(root->right);
             
        }
    }
    void recoverTree(TreeNode *root) {
        pre = NULL;
        first = NULL;
        second= NULL;
        inOrder(root);
        int val;
        val = first->val;
        first->val=second->val;
        second->val=val;
        return;
    }
http://www.cnblogs.com/yuzhangcmu/p/4208319.html
X. Iterative Version:
https://discuss.leetcode.com/topic/24975/java-solution-with-in-order-traversal/2
public void recoverTree(TreeNode root) {
    if (root == null)
        return;
    Stack<TreeNode> stack = new Stack<TreeNode> ();
    List<TreeNode> violates = new ArrayList<TreeNode> ();
    TreeNode pre = null;
    TreeNode cur = root;
    while (cur != null || !stack.isEmpty()) {
        if (cur != null) {
            stack.push(cur);
            cur = cur.left;
        } else{
            cur = stack.pop();
            if (pre != null && cur.val < pre.val) {
                if (violates.size() == 0) {
                    violates.add(pre);
                    violates.add(cur);
                } else{
                    violates.set(1, cur);
                }
            }
            pre = cur;
            cur = cur.right;
        }
    }
    if (violates.size() != 0) {
        int temp = violates.get(0).val;
        violates.get(0).val = violates.get(1).val;
        violates.get(1).val = temp;
    }
}
1 public void recoverTree1(TreeNode root) {
 2         if (root == null) {
 3             return;
 4         }
 5         
 6         TreeNode node1 = null;
 7         TreeNode node2 = null;
 9         TreeNode pre = null; 
10         
11         Stack<TreeNode> s = new Stack<TreeNode>();
12         TreeNode cur = root;
13         
14         while (true) {
15             while (cur != null) {
16                 s.push(cur);
17                 cur = cur.left;
18             }
19             
20             if (s.isEmpty()) {
21                 break;
22             }
23             
24             TreeNode node = s.pop();
25             
26             if (pre != null) {
27                 // invalid order
28                 if (pre.val > node.val) {
29                     if (node1 == null) {
30                         node1 = pre;
31                         node2 = node;
32                     } else {
33                         node2 = node;
34                     }
35                 }
36             }
38             pre = node;
40             cur = node.right;
41         }
43         int tmp = node1.val;
44         node1.val = node2.val;
45         node2.val = tmp;
47         return;
48     }

X. O(nlogn)
https://discuss.leetcode.com/topic/29297/18ms-java-solution-with-in-order-traversal-and-sorting-o-nlogn-time-and-o-n-space
    public void recoverTree(TreeNode root) {
        // in-order traversal of treenodes, followed by sorting and reassignment of values
        List<TreeNode> inorder = inorder(root);
        List<Integer> inorderNumbers = new ArrayList<Integer>();
        for (TreeNode node : inorder) {
            inorderNumbers.add(node.val);
        }
        inorderNumbers.sort(null);
        for (int i = 0; i < inorder.size(); i++) {
            inorder.get(i).val = inorderNumbers.get(i);
        }
    }
    
    private List<TreeNode> inorder (TreeNode root) {
        List<TreeNode> result = new ArrayList<TreeNode>();
        if (root == null) {
            return result;
        }
        result.addAll(inorder(root.left));
        result.add(root);
        result.addAll(inorder(root.right));
        return result;
    }

X. Using Morris traverse:
To find out the bad nodes, we have to perform an inorder traversal. So the question becomes: Can we conduct an inorder traversal with constant space usage?
Morris Traversal, that implement inorder traversal without recursion or stack.
http://www.lifeincode.net/programming/leetcode-recover-binary-search-tree-java/
The Morris traverse is like the following.
Firstly, take the root node as current node.
Then there are two possibilities.
  1. If current node doesn’t have left child, output the value. And current = current.right.
  2. If current node has left child, try to find the precursor node of current node, which is the right-most node of the left child of current. If the right child of it is null (If we don’t modify the tree, it should be null), set current as its right child, and current = current.left. Otherwise (It means that we have modify the tree and we have traverse all nodes in the left subtree of current node), set it to null, output current. And current = current.right.
During the traverse, we can find the nodes which are needed to be swapped.
But what about the time complexity? In fact, we only visit every edge twice. One is for going to that node, and another one is for searching the precursor node. There are only n1 edges in a tree. So the time complexity is also O(n).
The Morris traverse can also be used in pre-order and post-order traverse. There is a great article, including the detail pictures. You can visit http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html if you are interested.
    public void recoverTree(TreeNode root) {
        TreeNode current = root;
        TreeNode prev = null;
        TreeNode node1 = null;
        TreeNode node2 = null;
        while (current != null) {
            if (current.left == null) {
                if (prev != null) {
                    if (prev.val >= current.val) {
                        if (node1 == null)
                            node1 = prev;
                        node2 = current;
                    }
                }
                prev = current;
                current = current.right;
            } else {
                TreeNode t = current.left;
                while (t.right != null && t.right != current)
                    t = t.right;
                if (t.right == null) {
                    t.right = current;
                    current = current.left;
                } else {
                    t.right = null;
                    if (prev != null) {
                        if (prev.val >= current.val) {
                            if (node1 == null)
                                node1 = prev;
                            node2 = current;
                        }
                    }
                    prev = current;
                    current = current.right;
                }
            }
        }
        int tmp = node1.val;
        node1.val = node2.val;
        node2.val = tmp;
    }
Morris Traversal中序遍历的原理比较简单:
  • 如果当前节点的左孩子为空,则输出当前节点并将其有孩子作为当前节点。
  • 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点,也就是当前节点左子树的最右边的那个节点。
    • 如果前驱节点的右孩子为空,则将它的右孩子设置为当前节点,当前节点更新为其左孩子。
    • 如果前驱节点的右孩子为当前节点,则将前驱节点的右孩子设为空,输出当前节点,当前节点更新为其右孩子。
重复上述过程,直到当前节点为空,递归的时候我们同时需要记录错误的节点。那么我们如何知道一个节点的数据是不是有问题呢?对于中序遍历来说,假设当前节点为cur,它的前驱节点为pre,如果cur的值小于pre的值,那么cur和pre里面的数据就是交换的了。
As far as I know, there are more than one definition of BST, the differences of which are mainly about duplicate nodes.
Does the BST include duplicate nodes? If it does, place the duplicate nodes to the left child or the right child?

Yes, it is a great question in a real interview. And, in LeetCode, this is no duplicated node.

Read full article from Yu's Coding Garden : leetcode Question 75: Recover Binary Search Tree

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts