Facebook Pixel

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 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) and right points to Node 2
  • Node 2's left points to Node 1 and right points to Node 3
  • Node 3's left points to Node 2 and right points to Node 4
  • Node 4's left points to Node 3 and right points to Node 5
  • Node 5's left points to Node 4 and right 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:

  1. In-order Traversal: We need to process BST nodes in sorted order, which is naturally achieved through in-order DFS traversal (left → root → right).

  2. 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.

  3. 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
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. We encounter nodes in ascending order (thanks to BST property)
  2. Each node we visit should be linked to the previous node we visited
  3. The previous node's right should point to the current node
  4. 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 list
  • prev: Points to the previously processed node during traversal

Step-by-step Implementation:

  1. Base Case Handling: If the root is None, return None immediately as there's nothing to convert.

  2. Initialize Tracking Variables: Set both head and prev to None before starting the traversal.

  3. 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)
  4. 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)
    • If prev is None (first node encountered):
      • Set head = root (mark this as the start of our list)
    • Update prev = root for the next iteration
  5. Making it Circular: After the DFS completes:

    • prev now points to the last (largest) node
    • head points to the first (smallest) node
    • Connect them: prev.right = head and head.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) where n is the number of nodes (each node is visited exactly once)
  • Space: O(h) where h 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 Evaluator

Example 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 is None, 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
  • 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
  • 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
  • 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:

  1. Process left subtree first
  2. Then process current node (modify pointers)
  3. 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More