897. Increasing Order Search Tree
Problem Description
The problem statement presents a binary search tree (BST) and asks for a transformation of this BST into a new tree that is essentially a linked list in ascending order. In other words, the task is to rearrange the tree such that it maintains the in-order sequence of values, where every node only has a right child, effectively eliminating all the left children. The leftmost node of the original BST should become the new root of the so-called 'flattened' tree.
Flowchart Walkthrough
Let's analyze LeetCode problem 897, "Increasing Order Search Tree," using the algorithm flowchart available at the Flowchart. Here’s how to apply the flowchart to determine the appropriate algorithm:
Is it a graph?
- Yes: The data structure in this problem is a binary search tree, which is a type of graph.
Is it a tree?
- Yes: Specifically, it is a binary tree.
Following the decision that it is a tree allows us to consider the appropriate algorithm:
- The node directly indicates use of "DFS."
Conclusion: Based on our analysis using the flowchart, Depth-First Search (DFS) is the suggested approach for reorganizing the binary search tree into a linked list in increasing order.
Intuition
The solution leverages the property of BSTs where an in-order traversal yields the nodes in ascending order. The concept here is to perform an in-order Depth-First Search (DFS) traversal, and as we visit each node, we rearrange the pointers to create the 'right-skewed' tree desired for the problem.
The intuition behind the approach can be broken down into steps:
- Initialize a 'dummy' node as a precursor to the new root to make attachment easier.
- Start the in-order DFS from the root of the original BST.
- As the in-order DFS visits the nodes, adjust pointers such that each visited node becomes the right child of the previously visited node ('prev').
- The current node's left child is set to
None
. - The current node's right child becomes the entry point for the next nodes that are yet to be visited in the DFS.
- The current node's left child is set to
- The 'prev' node, initialized as the dummy node, is updated in each step to the current node.
- After the traversal, the right child of the dummy node (which was our starting point) now points to the new root of the in-order rearranged tree.
- The leftmost node from the original tree is now acting as the root of this reordered straight line tree.
This approach ensures that every node, except for the first (new root), will only have a single right child, fulfilling the condition of having no left child and at most one right child.
Learn more about Stack, Tree, Depth-First Search, Binary Search Tree and Binary Tree patterns.
Solution Approach
The solution implements an in-order traversal to process nodes of a binary search tree (BST) in the ascending order of their values. An in-order traversal visits nodes in the "left-root-right" sequence, which aligns with the BST property that left child nodes have smaller values than their parents, and right child nodes have larger values.
Here's a breakdown of the key parts of the solution:
-
An inner function
dfs
(short for Depth-First Search) is defined, which recursively traverses the BST in an in-order fashion. It helps us visit each node in the required sequence to rearrange the tree. -
A 'dummy' node is initiated before the traversal begins. This node serves as a placeholder to easily point to the new tree's root after the rearrangement is complete.
-
The
prev
variable points to the last processed node in the in-order sequence. Initially, it's set to the 'dummy' node to start the rearrangement process. -
During each call to
dfs
:- The left subtree is processed, ensuring that all smaller values have already been visited before we rearrange the current node.
- The current node (
root
) is detached from its left child by setting it toNone
. - The
right
attribute of theprev
node is set to the current node, effectively 'flattening' the tree by making the current node the next element in the list. - The
prev
variable is updated to the current node, preparing it to link to the next node in the traversal. - The right subtree is processed, continuing the in-order traversal.
-
Due to the
nonlocal
keyword usage, theprev
variable retains its value across differentdfs
invocations. -
After completing the DFS, the
right
child of the 'dummy' node is returned. This now points to the leftmost node of the original BST, which is the new root of the restructured 'tree' (now resembling a linked list). -
The end result is that all nodes are rearranged into a straight line, each having no left child, and only one right child, reflecting an in-order traversal of the original BST.
In summary, the solution uses a recursive in-order DFS traversal, pointer manipulation, and a 'dummy' placeholder node to create a modified tree that satisfies the specific conditions. It efficiently flattens the BST into a single right-skewed path that represents the ascending order of the original tree's values.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach with an example. Consider the following BST:
4 / \ 2 6 / \ / \ 1 3 5 7
According to the in-order traversal, the nodes are visited in the order: 1, 2, 3, 4, 5, 6, 7. We will flatten this BST to a right-skewed tree.
Here's step by step how the algorithm will work on this BST:
-
Initialize a
dummy
node as a precursor to the new root to make the attachment of nodes easier.- Let's say
dummy.val
is 0 for illustration purposes.
- Let's say
-
Start in-order DFS with the
dummy
node'sprev
pointing to it. -
Visit the leftmost node, which is 1. At this point:
- Set
prev.right = node 1
, disconnectnode 1
from its left (none in this case). - Update
prev
to benode 1
.
- Set
-
Visit node 2, the parent of node 1:
- Since node 1 right is null, set
node 1.right = node 2
and disconnectnode 2
from its left (which was node 1). - Update
prev
to benode 2
.
- Since node 1 right is null, set
-
Proceed to node 3 by visiting the right subtree of node 2:
- Set
node 2.right = node 3
and disconnectnode 3
from its left (none). - Update
prev
to benode 3
.
- Set
-
Continue with node 4 (root of the original BST) similarly:
- Set
node 3.right = node 4
and disconnectnode 4
from its left (which was node 2). - Update
prev
to benode 4
.
- Set
-
Move to node 5 through the left part of the right subtree of node 4:
- Set
node 4.right = node 5
and disconnectnode 5
from its left (none). - Update
prev
to benode 5
.
- Set
-
Node 6 is processed next:
- Set
node 5.right = node 6
and disconnectnode 6
from its left (which was node 5). - Update
prev
to benode 6
.
- Set
-
Finally, visit node 7:
- Set
node 6.right = node 7
and disconnectnode 7
from its left (none). - Update
prev
to benode 7
.
- Set
-
At the end of this DFS process, the BST has been turned into a right-skewed list:
0 - 1 - 2 - 3 - 4 - 5 - 6 - 7
- The
dummy
node's right child (0's right child) is node 1, so this is the new root of the flattened tree:
1 - 2 - 3 - 4 - 5 - 6 - 7
We've now flattened the original BST to a linked list in ascending order following the in-order sequence of the BST. Each node has no left child and only one right child.
Solution Implementation
1class TreeNode:
2 # TreeNode class definition remains the same
3 def __init__(self, val=0, left=None, right=None):
4 self.val = val
5 self.left = left
6 self.right = right
7
8class Solution:
9 def increasingBST(self, root: TreeNode) -> TreeNode:
10 # Helper function to perform in-order traversal
11 def in_order_traversal(node):
12 # Base case: if node is None, return
13 if not node:
14 return
15
16 # Traverse the left subtree
17 in_order_traversal(node.left)
18
19 # Visit the current node:
20 # Since we are rearranging the tree into a right-skewed tree,
21 # we make the previously visited node's right point to the current node
22 self.prev.right = node
23
24 # Disconnect the current node's left child
25 node.left = None
26
27 # Update the previous node to be the current node
28 self.prev = node
29
30 # Traverse the right subtree
31 in_order_traversal(node.right)
32
33 # Dummy node to start the right-skewed tree
34 dummy = TreeNode()
35 # 'prev' initially points to the dummy node
36 self.prev = dummy
37
38 # Perform in-order traversal; this will rearrange the nodes
39 in_order_traversal(root)
40
41 # The right child of the dummy node is the root of the modified tree
42 return dummy.right
43
1class Solution {
2 // 'prev' will be used to keep track of the previous node in inorder traversal.
3 private TreeNode previousNode;
4
5 public TreeNode increasingBST(TreeNode root) {
6 // 'dummyNode' will act as a placeholder to the beginning of the resulting increasing BST.
7 TreeNode dummyNode = new TreeNode(0);
8 previousNode = dummyNode;
9
10 // Perform the 'inorder' traversal starting from the root.
11 inorderTraversal(root);
12
13 // Return the right child of the dummy node, which is the real root of the increasing BST.
14 return dummyNode.right;
15 }
16
17 // The 'inorderTraversal' function recursively traverses the tree in an inorder fashion.
18 private void inorderTraversal(TreeNode node) {
19 // If the current node is 'null', we have reached the end and should return.
20 if (node == null) {
21 return;
22 }
23
24 // Recurse on the left subtree.
25 inorderTraversal(node.left);
26
27 // During the inorder traversal, we reassign the rights of the 'previousNode' to the current 'node'
28 // and nullify the left child to adhere to increasing BST rules.
29 previousNode.right = node;
30 node.left = null;
31
32 // Update 'previousNode' to the current node.
33 previousNode = node;
34
35 // Recurse on the right subtree.
36 inorderTraversal(node.right);
37 }
38}
39
1// Declaration for a binary tree node.
2struct TreeNode {
3 int val;
4 TreeNode *left;
5 TreeNode *right;
6 // Constructor for a new empty TreeNode.
7 TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 // Constructor for a TreeNode with a specific value.
9 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
10 // Constructor for a TreeNode with a value and specified left and right children.
11 TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
12};
13
14class Solution {
15public:
16 TreeNode* previousNode; // Node to keep track of the previously processed node.
17
18 // Takes a binary search tree and rearranges it so that it becomes a strictly increasing order tree
19 // where each node only has a right child following an in-order traversal of the tree.
20 TreeNode* increasingBST(TreeNode* root) {
21 TreeNode* dummyNode = new TreeNode(0); // Create a dummy node that acts as the previous node of the first node in the in-order traversal.
22 previousNode = dummyNode; // Initialize previousNode with the dummy node.
23
24 inOrderTraversal(root); // Start the in-order traversal and rearrange the tree.
25
26 // Return the right child of the dummy node, which is the new root of the rearranged tree.
27 return dummyNode->right;
28 }
29
30 // Helper function that performs DFS in-order traversal of the binary tree.
31 void inOrderTraversal(TreeNode* currentNode) {
32 if (!currentNode) return; // If the current node is null, return.
33
34 // Traverse the left subtree first (in-order).
35 inOrderTraversal(currentNode->left);
36
37 previousNode->right = currentNode; // Assign the current node to the right child of the previous node.
38 currentNode->left = nullptr; // The current node should not have a left child in the rearranged tree.
39 previousNode = currentNode; // Update the previousNode to be the current node after processing it.
40
41 // Traverse the right subtree next (in-order).
42 inOrderTraversal(currentNode->right);
43 }
44};
45
1// Interface for a binary tree node.
2interface TreeNode {
3 val: number;
4 left: TreeNode | null;
5 right: TreeNode | null;
6}
7
8// Function to create a new TreeNode with a specific value.
9function createTreeNode(val: number, left: TreeNode | null = null, right: TreeNode | null = null): TreeNode {
10 return {
11 val: val,
12 left: left,
13 right: right
14 };
15}
16
17let previousNode: TreeNode | null; // Variable to keep track of the previously processed node.
18
19// Function that takes a binary search tree and rearranges it so that it becomes a strictly
20// increasing order tree where each node only has a right child following an in-order traversal
21// of the tree.
22function increasingBST(root: TreeNode): TreeNode {
23 const dummyNode: TreeNode = createTreeNode(0); // Create a dummy node that will act as the "previous node" of the first node in the in-order traversal.
24 previousNode = dummyNode; // Initialize previousNode with the dummy node.
25
26 // Start the in-order traversal and rearrange the tree.
27 inOrderTraversal(root);
28
29 // Return the right child of the dummy node, which is the new root of the rearranged tree.
30 return dummyNode.right!;
31}
32
33// Helper function that performs a depth-first search (DFS) in-order traversal of the binary tree.
34function inOrderTraversal(currentNode: TreeNode | null): void {
35 if (currentNode === null) return; // If the current node is null, do nothing and return.
36
37 // First, traverse the left subtree (in-order).
38 inOrderTraversal(currentNode.left);
39
40 // Assign the current node to the right child of the previous node.
41 if (previousNode) previousNode.right = currentNode;
42
43 // The current node should not have a left child in the rearranged tree.
44 currentNode.left = null;
45
46 // Update the previousNode to be the current node after processing it.
47 previousNode = currentNode;
48
49 // Next, traverse the right subtree (in-order).
50 inOrderTraversal(currentNode.right);
51}
52
Time and Space Complexity
The time complexity of the code is O(n)
where n
is the number of nodes in the binary tree. This is because the solution involves performing a depth-first search (dfs) traversal of the tree, which visits each node exactly once.
The space complexity of the code is also O(n)
in the worst-case scenario, which occurs when the binary tree is completely unbalanced (e.g., every node only has a left child, forming a linear chain). In this case, the recursion stack depth would be n
. For balanced trees, the space complexity would be O(h)
where h
is the height of the tree, since the depth of recursion would only be as deep as the height of the tree.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https algomonster s3 us east 2 amazonaws com cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS
Want a Structured Path to Master System Design Too? Don’t Miss This!