426. Convert Binary Search Tree to Sorted Doubly Linked List 🔒
Problem Description
You are given the root of a Binary Search Tree (BST) and need to convert it into a sorted Circular Doubly-Linked List. The conversion must be done in-place, meaning you should modify the existing tree nodes rather than creating new ones.
In the conversion:
- The
left
pointer of each node should point to its predecessor (the previous node in sorted order) - The
right
pointer of each node should point to its successor (the next node in sorted order) - The list should be circular, meaning:
- The
right
pointer of the last element should point to the first element - The
left
pointer of the first element should point to the last element
- The
The BST property ensures that an in-order traversal gives us nodes in sorted order. After the transformation, you should return a pointer to the smallest element (the head) of the resulting circular doubly-linked list.
For example, if you have a BST with values that would be [1, 2, 3, 4, 5]
in sorted order, after conversion:
- Node 1's
left
points to Node 5 (circular connection) andright
points to Node 2 - Node 2's
left
points to Node 1 andright
points to Node 3 - Node 3's
left
points to Node 2 andright
points to Node 4 - Node 4's
left
points to Node 3 andright
points to Node 5 - Node 5's
left
points to Node 4 andright
points to Node 1 (circular connection)
If the input root is None
, return None
.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: While this problem involves a Binary Search Tree, a tree is a special type of graph (specifically, an acyclic connected graph). The BST has nodes connected by edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a Binary Search Tree, which is a tree data structure.
DFS
- Yes: We arrive at DFS (Depth-First Search) as our solution approach.
Conclusion: The flowchart suggests using DFS for this problem.
Why DFS is the Right Choice
The DFS pattern is perfect for this problem because:
-
In-order Traversal: We need to process BST nodes in sorted order, which is naturally achieved through in-order DFS traversal (left → root → right).
-
Sequential Processing: During the traversal, we need to maintain connections between consecutive nodes in sorted order. DFS allows us to process nodes one by one while keeping track of the previously visited node.
-
Tree Structure Navigation: DFS efficiently navigates the tree structure, visiting each node exactly once, which is ideal for our in-place transformation requirement.
The solution uses DFS with in-order traversal to:
- Visit nodes in ascending order
- Link each node with its predecessor using the
prev
pointer - Track the head (first/smallest node) for the final circular connection
- Connect the last node back to the first to complete the circular structure
Intuition
The key insight is recognizing that a BST's in-order traversal gives us nodes in sorted order. Since we need a sorted doubly-linked list, we can leverage this property by visiting nodes in exactly the order we want them to appear in our final list.
Think of the transformation process like threading a needle through beads in order. As we traverse the tree in-order:
- We encounter nodes in ascending order (thanks to BST property)
- Each node we visit should be linked to the previous node we visited
- The previous node's
right
should point to the current node - The current node's
left
should point to the previous node
The challenge is keeping track of the "previous" node as we recursively traverse the tree. This is where the prev
variable becomes crucial - it acts as our memory of the last node we processed.
When we start, we don't have a previous node, so when we encounter the first (smallest) node, we mark it as the head
. This is important because we'll need to connect the last node back to this head to make the list circular.
The beauty of this approach is that we're essentially "flattening" the tree as we traverse it. Each recursive call to the left subtree processes all smaller values first, then we process the current node (linking it with the previous), and finally we process all larger values in the right subtree.
After the entire traversal completes, we have a linear doubly-linked list from smallest to largest. The final step is making it circular by connecting the last node (which prev
points to after traversal) back to the first node (head
), completing the circular structure.
This solution elegantly combines the BST's sorted property with DFS traversal to achieve an in-place transformation with O(n)
time complexity and O(h)
space complexity (where h
is the height of the tree, used by the recursion stack).
Solution Approach
The implementation uses an in-order DFS traversal with two key variables to track the transformation:
head
: Points to the first (smallest) node in the listprev
: Points to the previously processed node during traversal
Step-by-step Implementation:
-
Base Case Handling: If the root is
None
, returnNone
immediately as there's nothing to convert. -
Initialize Tracking Variables: Set both
head
andprev
toNone
before starting the traversal. -
DFS In-order Traversal: The
dfs
function follows the standard in-order pattern:dfs(root.left) # Process left subtree (smaller values) process current # Process current node dfs(root.right) # Process right subtree (larger values)
-
Node Processing Logic: When processing the current node:
- If
prev
exists (not the first node):- Set
prev.right = root
(previous node's successor points to current) - Set
root.left = prev
(current node's predecessor points to previous)
- Set
- If
prev
isNone
(first node encountered):- Set
head = root
(mark this as the start of our list)
- Set
- Update
prev = root
for the next iteration
- If
-
Making it Circular: After the DFS completes:
prev
now points to the last (largest) nodehead
points to the first (smallest) node- Connect them:
prev.right = head
andhead.left = prev
Why nonlocal
is Used: The nonlocal
keyword allows the nested dfs
function to modify the prev
and head
variables from the outer scope, maintaining state across recursive calls.
Time and Space Complexity:
- Time:
O(n)
wheren
is the number of nodes (each node is visited exactly once) - Space:
O(h)
whereh
is the height of the tree (recursion stack depth)
The elegance of this solution lies in its simplicity - by processing nodes in their natural sorted order and maintaining just two pointers, we achieve the complete transformation in a single traversal.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through converting a small BST to illustrate the solution approach:
Initial BST: 4 / \ 2 5 / \ 1 3
Goal: Convert to circular doubly-linked list: 1 ↔ 2 ↔ 3 ↔ 4 ↔ 5 (with 5 ↔ 1 circular connection)
Initial State: head = None
, prev = None
Step 1: DFS reaches node 1 (leftmost)
- Traverse:
dfs(4)
→dfs(2)
→dfs(1)
→dfs(None)
returns - Process node 1:
prev
isNone
, so this is our first node- Set
head = 1
- Update
prev = 1
- Continue:
dfs(1.right)
→dfs(None)
returns
Step 2: Back to node 2
- Process node 2:
prev = 1
exists, so link them:prev.right = 2
(1's right now points to 2)2.left = 1
(2's left now points to 1)
- Update
prev = 2
- Current state:
1 ↔ 2
Step 3: Process node 3
- Traverse:
dfs(3)
→dfs(None)
returns - Process node 3:
- Link with
prev = 2
:2.right = 3
3.left = 2
- Update
prev = 3
- Link with
- Current state:
1 ↔ 2 ↔ 3
Step 4: Back to node 4 (root)
- Process node 4:
- Link with
prev = 3
:3.right = 4
4.left = 3
- Update
prev = 4
- Link with
- Current state:
1 ↔ 2 ↔ 3 ↔ 4
Step 5: Process node 5
- Traverse:
dfs(5)
→dfs(None)
returns - Process node 5:
- Link with
prev = 4
:4.right = 5
5.left = 4
- Update
prev = 5
- Link with
- Current state:
1 ↔ 2 ↔ 3 ↔ 4 ↔ 5
Step 6: Make it circular
- After DFS completes:
head = 1
,prev = 5
- Connect the ends:
prev.right = head
(5's right points to 1)head.left = prev
(1's left points to 5)
Final Result: Circular doubly-linked list where:
- Each node's
left
points to its predecessor - Each node's
right
points to its successor - The list forms a complete circle: 1 ↔ 2 ↔ 3 ↔ 4 ↔ 5 ↔ (back to 1)
The transformation reuses the original nodes' left
and right
pointers, achieving an in-place conversion with just one traversal.
Solution Implementation
1"""
2# Definition for a Node.
3class Node:
4 def __init__(self, val, left=None, right=None):
5 self.val = val
6 self.left = left
7 self.right = right
8"""
9
10from typing import Optional
11
12
13class Solution:
14 def treeToDoublyList(self, root: Optional['Node']) -> Optional['Node']:
15 """
16 Convert a Binary Search Tree to a sorted circular doubly-linked list in-place.
17
18 Args:
19 root: The root node of the BST
20
21 Returns:
22 The head of the circular doubly-linked list
23 """
24
25 def inorder_traversal(node: Optional['Node']) -> None:
26 """
27 Perform in-order traversal and build doubly-linked list connections.
28
29 Args:
30 node: Current node being processed
31 """
32 if node is None:
33 return
34
35 nonlocal previous_node, list_head
36
37 # Process left subtree first (smaller values)
38 inorder_traversal(node.left)
39
40 # Process current node
41 if previous_node:
42 # Connect previous node to current node
43 previous_node.right = node
44 node.left = previous_node
45 else:
46 # First node encountered becomes the head
47 list_head = node
48
49 # Update previous node to current node
50 previous_node = node
51
52 # Process right subtree (larger values)
53 inorder_traversal(node.right)
54
55 # Handle empty tree case
56 if root is None:
57 return None
58
59 # Initialize variables to track list head and previous node
60 list_head = None
61 previous_node = None
62
63 # Build the doubly-linked list through in-order traversal
64 inorder_traversal(root)
65
66 # Connect tail to head to make it circular
67 previous_node.right = list_head
68 list_head.left = previous_node
69
70 return list_head
71
1/*
2// Definition for a Node.
3class Node {
4 public int val;
5 public Node left;
6 public Node right;
7
8 public Node() {}
9
10 public Node(int _val) {
11 val = _val;
12 }
13
14 public Node(int _val, Node _left, Node _right) {
15 val = _val;
16 left = _left;
17 right = _right;
18 }
19};
20*/
21
22class Solution {
23 // Previous node in the doubly linked list during in-order traversal
24 private Node prev;
25 // Head of the doubly linked list (smallest element)
26 private Node head;
27
28 /**
29 * Converts a Binary Search Tree to a sorted circular doubly linked list in-place.
30 * The left pointer acts as the previous pointer, and the right pointer acts as the next pointer.
31 *
32 * @param root The root of the binary search tree
33 * @return The head of the circular doubly linked list
34 */
35 public Node treeToDoublyList(Node root) {
36 // Handle empty tree
37 if (root == null) {
38 return null;
39 }
40
41 // Initialize pointers
42 prev = null;
43 head = null;
44
45 // Perform in-order traversal to build the doubly linked list
46 inOrderTraversal(root);
47
48 // Connect the last node with the first node to make it circular
49 prev.right = head; // Last node's next points to head
50 head.left = prev; // Head's previous points to last node
51
52 return head;
53 }
54
55 /**
56 * Performs in-order traversal of the BST and builds the doubly linked list.
57 * In-order traversal ensures nodes are processed in sorted order.
58 *
59 * @param node The current node being processed
60 */
61 private void inOrderTraversal(Node node) {
62 // Base case: reached a null node
63 if (node == null) {
64 return;
65 }
66
67 // Process left subtree first (smaller values)
68 inOrderTraversal(node.left);
69
70 // Process current node
71 if (prev != null) {
72 // Connect previous node to current node
73 prev.right = node; // Previous node's next points to current
74 node.left = prev; // Current node's previous points to prev
75 } else {
76 // First node encountered (smallest value) becomes the head
77 head = node;
78 }
79
80 // Update prev to current node for next iteration
81 prev = node;
82
83 // Process right subtree (larger values)
84 inOrderTraversal(node.right);
85 }
86}
87
1/*
2// Definition for a Node.
3class Node {
4public:
5 int val;
6 Node* left;
7 Node* right;
8
9 Node() {}
10
11 Node(int _val) {
12 val = _val;
13 left = NULL;
14 right = NULL;
15 }
16
17 Node(int _val, Node* _left, Node* _right) {
18 val = _val;
19 left = _left;
20 right = _right;
21 }
22};
23*/
24
25class Solution {
26private:
27 Node* prev; // Pointer to track the previously processed node in inorder traversal
28 Node* head; // Pointer to the head of the doubly linked list
29
30public:
31 /**
32 * Converts a Binary Search Tree to a sorted circular doubly linked list in-place.
33 * The left pointer acts as the previous pointer, and the right pointer acts as the next pointer.
34 *
35 * @param root The root of the binary search tree
36 * @return The head of the circular doubly linked list
37 */
38 Node* treeToDoublyList(Node* root) {
39 // Handle empty tree case
40 if (!root) {
41 return nullptr;
42 }
43
44 // Initialize pointers
45 prev = nullptr;
46 head = nullptr;
47
48 // Perform inorder traversal to build the doubly linked list
49 inorderTraversal(root);
50
51 // Connect the tail to head to make it circular
52 prev->right = head; // Last node's next points to head
53 head->left = prev; // Head's previous points to last node
54
55 return head;
56 }
57
58private:
59 /**
60 * Performs inorder traversal of the BST and converts it to a doubly linked list.
61 * During traversal, nodes are linked in sorted order.
62 *
63 * @param node Current node being processed
64 */
65 void inorderTraversal(Node* node) {
66 // Base case: null node
67 if (!node) {
68 return;
69 }
70
71 // Process left subtree first (smaller values)
72 inorderTraversal(node->left);
73
74 // Process current node
75 if (prev) {
76 // Link previous node to current node
77 prev->right = node; // Previous node's next points to current
78 node->left = prev; // Current node's previous points to prev
79 } else {
80 // First node in inorder traversal becomes the head
81 head = node;
82 }
83
84 // Update prev to current node for next iteration
85 prev = node;
86
87 // Process right subtree (larger values)
88 inorderTraversal(node->right);
89 }
90};
91
1/**
2 * Definition for a Node.
3 */
4interface Node {
5 val: number;
6 left: Node | null;
7 right: Node | null;
8}
9
10/**
11 * Converts a Binary Search Tree to a sorted circular doubly-linked list in-place.
12 * The list should be sorted in ascending order.
13 *
14 * @param root - The root node of the binary search tree
15 * @returns The head of the converted circular doubly-linked list
16 */
17function treeToDoublyList(root: Node | null): Node | null {
18 // Handle empty tree case
19 if (!root) return root;
20
21 // Track the previous node during in-order traversal
22 let previousNode: Node | null = null;
23 // Track the head of the doubly-linked list (leftmost node)
24 let headNode: Node | null = null;
25
26 /**
27 * Performs in-order traversal to convert BST to doubly-linked list
28 * In-order traversal ensures nodes are processed in sorted order
29 *
30 * @param currentNode - The current node being processed
31 */
32 function inOrderTraversal(currentNode: Node | null): void {
33 // Base case: reached null node
34 if (!currentNode) return;
35
36 // Process left subtree first (smaller values)
37 inOrderTraversal(currentNode.left);
38
39 // Process current node
40 if (previousNode) {
41 // Link previous node to current node
42 previousNode.right = currentNode;
43 currentNode.left = previousNode;
44 } else {
45 // First node encountered (leftmost) becomes the head
46 headNode = currentNode;
47 }
48
49 // Update previous node for next iteration
50 previousNode = currentNode;
51
52 // Process right subtree (larger values)
53 inOrderTraversal(currentNode.right);
54 }
55
56 // Perform the conversion
57 inOrderTraversal(root);
58
59 // Make the list circular by connecting tail to head
60 previousNode!.right = headNode;
61 headNode!.left = previousNode;
62
63 return headNode;
64}
65
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary search tree. The algorithm performs an in-order traversal of the tree using DFS, visiting each node exactly once. During each visit, it performs constant time operations (updating pointers), so the overall time complexity is linear with respect to the number of nodes.
Space Complexity: O(h)
where h
is the height of the binary search tree. The space complexity is determined by the recursive call stack used during the DFS traversal. In the worst case (skewed tree), the height can be O(n)
, making the space complexity O(n)
. In the best case (balanced tree), the height is O(log n)
, making the space complexity O(log n)
. The algorithm uses only a constant amount of extra space for the head
and prev
variables, which doesn't affect the overall space complexity dominated by the recursion stack.
Common Pitfalls
1. Forgetting to Make the List Circular
One of the most common mistakes is correctly building the doubly-linked list but forgetting to connect the last node back to the first node to make it circular.
Incorrect Implementation:
def treeToDoublyList(self, root):
if not root:
return None
head = None
prev = None
def dfs(node):
nonlocal prev, head
if not node:
return
dfs(node.left)
if prev:
prev.right = node
node.left = prev
else:
head = node
prev = node
dfs(node.right)
dfs(root)
# MISTAKE: Forgot to connect tail to head!
return head
Why This Fails: The resulting structure would be a regular doubly-linked list, not a circular one. The last node's right
pointer would still be None
, and the first node's left
pointer would still be None
.
Correct Solution: Always remember to add the circular connections after the traversal:
# After traversal completes prev.right = head # Last node points to first head.left = prev # First node points to last
2. Not Using nonlocal
or Incorrectly Managing State
Another common pitfall is trying to track head
and prev
without properly declaring them as nonlocal
in the nested function.
Incorrect Implementation:
def treeToDoublyList(self, root):
if not root:
return None
def dfs(node, prev=None, head=None):
# MISTAKE: Trying to pass prev and head as parameters
if not node:
return prev, head
prev, head = dfs(node.left, prev, head)
if prev:
prev.right = node
node.left = prev
else:
head = node
prev = node
return dfs(node.right, prev, head)
# This approach becomes complex and error-prone
Why This Fails: Without nonlocal
, the nested function creates local copies of variables, and changes won't persist across recursive calls. Passing them as parameters makes the code unnecessarily complex and harder to debug.
Correct Solution: Use nonlocal
to maintain state across all recursive calls:
head = None
prev = None
def dfs(node):
nonlocal prev, head # Allows modification of outer scope variables
# ... rest of the logic
3. Modifying Node Pointers Before Processing Children
A subtle but critical mistake is modifying the current node's pointers before recursively processing its children.
Incorrect Implementation:
def dfs(node):
nonlocal prev, head
if not node:
return
# MISTAKE: Modifying pointers before processing left subtree
if prev:
prev.right = node
node.left = prev # This destroys the original left pointer!
else:
head = node
prev = node
dfs(node.left) # node.left was already modified above!
dfs(node.right)
Why This Fails: Modifying node.left
before calling dfs(node.left)
destroys the tree structure, causing the traversal to fail or skip nodes.
Correct Solution: Always follow the in-order traversal pattern strictly:
- Process left subtree first
- Then process current node (modify pointers)
- Finally process right subtree
def dfs(node):
nonlocal prev, head
if not node:
return
dfs(node.left) # Process left first (original pointer intact)
# Now safe to modify pointers
if prev:
prev.right = node
node.left = prev
else:
head = node
prev = node
dfs(node.right) # Process right last
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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 assets algo monster 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 As the name suggests
Want a Structured Path to Master System Design Too? Don’t Miss This!