Intuition
Let's start from straightforward but not optimal solution
with a linear time and space complexity.
This solution serves to identify and discuss all subproblems.
It's known that inorder traversal of BST is an array sorted in
the ascending order.
Here is how one could compute an inorder traversal
Here two nodes are swapped, and hence inorder traversal is
an almost sorted array where only two elements are swapped.
To identify two swapped elements in a sorted array is
a classical problem that could be solved in linear time.
Here is a solution code
When swapped nodes are known, one could traverse the tree
again and swap their values.
Algorithm
Here is the algorithm:
Construct inorder traversal of the tree.
It should be an almost sorted list where only two elements are swapped.
Identify two swapped elements x and y in an almost sorted array
in linear time.
Traverse the tree again. Change value x to y and value y to x.
Implementation
Complexity Analysis
Time complexity : O(N)\mathcal{O}(N). To compute inorder traversal takes
O(N)\mathcal{O}(N) time, to identify and to swap back swapped nodes O(N)\mathcal{O}(N) in the worst case.
Space complexity : O(N)\mathcal{O}(N) since we keep inorder traversal nums with N elements.
In approach 1 we discussed three easy subproblems of this hard problem:
Construct inorder traversal.
Find swapped elements in
an almost sorted array where only two elements are swapped.
Swap values of two nodes.
Now we will discuss three more approaches, and basically they are
all the same :
Merge steps 1 and 2, i.e. identify swapped nodes during the
inorder traversal.
Swap node values.
The difference in-between the following approaches is in a chosen
method to implement inorder traversal :
Approach 2 : Iterative.
Approach 3 : Recursive.
Approach 4 : Morris.
Iterative and recursive approaches here do less than one pass,
and they both need up to O(H)\mathcal{O}(H) space to keep stack,
where H is a tree height.
Morris approach is two pass approach, but it's a constant-space one.
Intuition
Here we construct inorder traversal by iterations
and identify swapped nodes at the same time, in one pass.
Iterative inorder traversal is simple:
go left as far as you can, then one step right. Repeat till
the end of nodes in the tree.
To identify swapped nodes,
track the last node pred in the inorder traversal (i.e. the
predecessor of the current node)
and compare it with current node value.
If the current node value is smaller than its predecessor pred value,
the swapped node is here.
There are only two swapped nodes here, and hence one could break after
having the second node identified.
Doing so, one could get directly nodes (and not only their values),
and hence swap node values in O(1)\mathcal{O}(1) time, drastically
reducing the time needed for step 3.
Implementation
Don't use Stack in Java, use ArrayDeque instead.
Complexity Analysis
Time complexity: O(N)\mathcal{O}(N) in the worst case
when one of the swapped nodes is a rightmost leaf.
Space complexity : up to O(N)\mathcal{O}(N) to keep the stack
in the worst case when the tree is completely lean.
Iterative approach 2 could be converted into recursive one.
Recursive inorder traversal is extremely simple:
follow Left->Node->Right direction, i.e. do the recursive call
for the left child, then do all the business with the node
(= if the node is the swapped one or not), and
then do the recursive call for the right child.
On the following figure the nodes are numerated in the order you visit them,
please follow 1-2-3-4-5 to compare different DFS strategies.
Implementation
Complexity Analysis
Time complexity: O(N)\mathcal{O}(N) in the worst case
when one of the swapped nodes is a rightmost leaf.
Space complexity : up to O(N)\mathcal{O}(N) to keep the stack
in the worst case when the tree is completely lean.
We discussed already iterative and recursive inorder traversals,
which both have great time complexity though use up to
O(N)\mathcal{O}(N) to keep stack.
We could trade in performance to save space.
The idea of Morris inorder traversal is simple:
to use no space but to traverse the tree.
How that could be even possible? At each node one has to decide where to go:
left or right, traverse left subtree or traverse right subtree.
How one could know that the left subtree is already done if no
additional memory is allowed?
The idea of Morris
algorithm is to set the temporary link between the node and its
predecessor:
predecessor.right = root.
So one starts from the node, computes its predecessor and
verifies if the link is present.
There is no link? Set it and go to the left subtree.
There is a link? Break it and go to the right subtree.
There is one small issue to deal with : what if there is no
left child, i.e. there is no left subtree?
Then go straightforward to the right subtree.
Implementation
Complexity Analysis
Time complexity : O(N)\mathcal{O}(N) since we visit each node up to
two times.
Space complexity : O(1)\mathcal{O}(1).