Facebook Pixel

285. Inorder Successor in BST 🔒

Problem Description

You are given the root of a binary search tree (BST) and a specific node p within that tree. Your task is to find and return the in-order successor of node p.

The in-order successor of a node p is defined as the node that comes immediately after p when performing an in-order traversal of the BST. In simpler terms, it's the node with the smallest value that is still greater than p.val.

Key points to understand:

  • In a BST, an in-order traversal visits nodes in ascending order of their values
  • The successor of node p must have a value greater than p.val
  • Among all nodes with values greater than p.val, the successor is the one with the minimum value
  • If no such node exists (meaning p has the largest value in the tree), return null

For example, if you have a BST with values [1, 2, 3, 4, 5] and p has value 3, then the in-order successor would be the node with value 4.

The solution leverages the BST property where left children have smaller values and right children have larger values. Starting from the root, we can navigate through the tree:

  • When we encounter a node with value greater than p.val, it could potentially be our answer, so we save it and explore the left subtree to find if there's a smaller valid successor
  • When we encounter a node with value less than or equal to p.val, we know the successor must be in the right subtree

This approach efficiently finds the successor in O(h) time where h is the height of the tree, without needing to perform a full in-order traversal.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary search tree is a special type of graph where each node has at most two children, and nodes are 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

  • We arrive at DFS: Since we're dealing with a tree structure, the flowchart leads us to use Depth-First Search.

Conclusion: The flowchart suggests using a DFS approach for finding the inorder successor in a BST.

The DFS pattern is indeed the correct choice here because:

  1. We need to traverse the tree structure to find the successor node
  2. The solution uses a modified DFS where we traverse down the tree following a specific path based on BST properties
  3. At each node, we make a decision to go left or right based on comparing values, which is a depth-first exploration strategy
  4. We don't need to visit all nodes - we follow a single path from root to leaf, making decisions at each level

The provided solution implements an iterative DFS that efficiently navigates through the BST by comparing the current node's value with p.val at each step, maintaining the potential successor in the ans variable as it traverses deeper into the tree.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about what an in-order successor means in a BST. When we do an in-order traversal of a BST, we get values in ascending order. So the successor of node p is simply the next value in this sorted sequence.

But do we really need to traverse the entire tree and create a sorted list? That would be inefficient. Instead, let's leverage the BST property: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger.

Starting from the root, when we encounter a node, we can ask: could this be the successor of p?

  • If current.val > p.val, this node could potentially be our answer! But wait - there might be a smaller value that's still greater than p.val in the left subtree. So we save this node as a candidate and explore left.

  • If current.val <= p.val, this node cannot be the successor (it's not greater than p). The successor must be somewhere in the right subtree, so we go right.

Think of it like playing a "higher or lower" game. Each time we find a value higher than p.val, we say "this could be it, but let me check if there's something smaller that still works" by going left. Each time we find a value that's not higher, we know we need to look for larger values by going right.

The beauty of this approach is that we're essentially performing a binary search on the tree. We maintain the best candidate we've seen so far (the smallest value greater than p.val), and keep refining our search until we've exhausted all possibilities. When we can't go any further (reach a null node), our saved candidate is the answer.

This way, we only visit nodes along a single path from root to leaf, giving us O(h) time complexity where h is the height of the tree, rather than O(n) if we did a full traversal.

Solution Approach

The solution implements a binary search approach on the BST to find the in-order successor efficiently.

We start with two key variables:

  • ans = None: This stores our potential answer (the smallest node value greater than p.val)
  • root: Our current position in the tree as we traverse

The algorithm follows a simple while loop that continues until we've exhausted all paths:

while root:
    if root.val > p.val:
        ans = root
        root = root.left
    else:
        root = root.right

Let's break down the logic:

Case 1: root.val > p.val

  • The current node's value is greater than p, so it could be our successor
  • We update ans = root to save this potential successor
  • But there might be a smaller valid successor in the left subtree, so we move left with root = root.left
  • This ensures we find the smallest value among all values greater than p.val

Case 2: root.val <= p.val

  • The current node's value is not greater than p, so it cannot be the successor
  • The successor must have a larger value, so we search the right subtree with root = root.right
  • We don't update ans because this node doesn't satisfy our requirement

The algorithm naturally terminates when root becomes None, meaning we've reached a leaf node's child. At this point, ans contains the in-order successor if it exists, or None if no successor was found.

Why this works:

  • Each time we find a node greater than p.val, we save it and try to find a smaller one by going left
  • Each time we find a node not greater than p.val, we look for larger values by going right
  • The last saved node in ans will be the smallest value greater than p.val, which is exactly the in-order successor

The time complexity is O(h) where h is the height of the tree, and space complexity is O(1) as we only use two variables regardless of tree size.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through finding the in-order successor of node p with value 6 in this BST:

        8
       / \
      3   10
     / \    \
    1   6   14
       / \
      4   7

We want to find the smallest node value greater than 6.

Initial State:

  • ans = None (no candidate yet)
  • root = 8 (starting at the root)
  • p.val = 6

Step 1: Current node = 8

  • Is 8 > 6? Yes!
  • This could be our answer, so ans = 8
  • But there might be a smaller valid successor in the left subtree
  • Move left: root = 3

Step 2: Current node = 3

  • Is 3 > 6? No
  • This can't be our successor (it's smaller than 6)
  • The successor must be larger, so go right
  • Move right: root = 6
  • Keep ans = 8 (no update)

Step 3: Current node = 6

  • Is 6 > 6? No (equal values don't count)
  • This can't be our successor
  • Need to find something larger, so go right
  • Move right: root = 7
  • Keep ans = 8 (no update)

Step 4: Current node = 7

  • Is 7 > 6? Yes!
  • This is a better (smaller) successor than 8
  • Update ans = 7
  • Check if there's an even smaller valid successor in the left subtree
  • Move left: root = None (7 has no left child)

Step 5: root = None

  • We've reached the end of our search path
  • Return ans = 7

Result: The in-order successor of node 6 is node 7.

Notice how we refined our answer from 8 to 7 by continuously searching for the smallest value that's still greater than 6. The algorithm efficiently navigated through only 4 nodes instead of traversing the entire tree.

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, x):
4#         self.val = x
5#         self.left = None
6#         self.right = None
7
8from typing import Optional
9
10
11class Solution:
12    def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:
13        """
14        Find the in-order successor of node p in a Binary Search Tree.
15      
16        The in-order successor is the node with the smallest value greater than p's value.
17      
18        Args:
19            root: The root of the BST
20            p: The target node whose successor we need to find
21          
22        Returns:
23            The in-order successor node, or None if it doesn't exist
24        """
25        # Initialize successor as None
26        successor = None
27      
28        # Traverse the BST from root
29        while root:
30            if root.val > p.val:
31                # Current node's value is greater than p's value
32                # This could be a potential successor
33                successor = root
34                # Look for a smaller successor in the left subtree
35                root = root.left
36            else:
37                # Current node's value is less than or equal to p's value
38                # The successor must be in the right subtree
39                root = root.right
40      
41        return successor
42
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode(int x) { val = x; }
8 * }
9 */
10class Solution {
11    /**
12     * Finds the inorder successor of a given node in a Binary Search Tree.
13     * The inorder successor is the node with the smallest value greater than the given node's value.
14     * 
15     * @param root The root of the Binary Search Tree
16     * @param p The target node whose successor we need to find
17     * @return The inorder successor node, or null if no successor exists
18     */
19    public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
20        // Variable to store the potential successor
21        TreeNode successor = null;
22      
23        // Traverse the tree starting from root
24        while (root != null) {
25            // If current node's value is greater than target node's value
26            if (root.val > p.val) {
27                // Current node could be the successor
28                // Update successor and search in left subtree for a closer successor
29                successor = root;
30                root = root.left;
31            } else {
32                // Current node's value is less than or equal to target node's value
33                // The successor must be in the right subtree
34                root = root.right;
35            }
36        }
37      
38        // Return the found successor (or null if none exists)
39        return successor;
40    }
41}
42
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
8 * };
9 */
10class Solution {
11public:
12    /**
13     * Find the inorder successor of a given node in a Binary Search Tree
14     * @param root: The root of the BST
15     * @param p: The node whose successor we need to find
16     * @return: The inorder successor node, or nullptr if it doesn't exist
17     */
18    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
19        // Initialize successor as nullptr
20        TreeNode* successor = nullptr;
21      
22        // Traverse the tree starting from root
23        while (root != nullptr) {
24            // If current node's value is greater than p's value,
25            // it could be a potential successor
26            if (root->val > p->val) {
27                // Update successor to current node
28                successor = root;
29                // Move to left subtree to find a smaller successor
30                root = root->left;
31            } 
32            else {
33                // Current node's value is less than or equal to p's value,
34                // so successor must be in the right subtree
35                root = root->right;
36            }
37        }
38      
39        // Return the found successor (or nullptr if none exists)
40        return successor;
41    }
42};
43
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 *     val: number
5 *     left: TreeNode | null
6 *     right: TreeNode | null
7 *     constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 *         this.val = (val===undefined ? 0 : val)
9 *         this.left = (left===undefined ? null : left)
10 *         this.right = (right===undefined ? null : right)
11 *     }
12 * }
13 */
14
15/**
16 * Finds the inorder successor of a given node in a Binary Search Tree.
17 * The inorder successor is the node with the smallest value greater than the given node's value.
18 * 
19 * @param root - The root of the binary search tree
20 * @param p - The target node whose successor we need to find
21 * @returns The inorder successor node, or null if no successor exists
22 */
23function inorderSuccessor(root: TreeNode | null, p: TreeNode | null): TreeNode | null {
24    // Initialize the successor candidate as null
25    let successor: TreeNode | null = null;
26  
27    // Traverse the tree starting from root
28    while (root !== null) {
29        // If current node's value is greater than target node's value
30        if (root.val > p!.val) {
31            // Current node could be the successor, update candidate
32            successor = root;
33            // Search in left subtree for a smaller successor
34            root = root.left;
35        } else {
36            // Current node's value is less than or equal to target
37            // Successor must be in the right subtree
38            root = root.right;
39        }
40    }
41  
42    // Return the found successor (or null if none exists)
43    return successor;
44}
45

Time and Space Complexity

The time complexity is O(h), where h is the height of the binary search tree. In the worst case, the algorithm traverses from the root to a leaf node, visiting at most h nodes. For a balanced BST, this would be O(log n) where n is the number of nodes, while for a skewed tree it could be O(n).

The space complexity is O(1). The algorithm uses only a constant amount of extra space - just the ans pointer variable to track the potential successor. The iterative approach doesn't use recursion, so there's no additional stack space required.

Common Pitfalls

Pitfall 1: Confusing with Finding Node p First

A common mistake is thinking you need to first locate node p in the tree and then find its successor from that position. This leads to unnecessarily complex code:

Incorrect Approach:

def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:
    # First find node p
    current = root
    while current and current.val != p.val:
        if p.val < current.val:
            current = current.left
        else:
            current = current.right
  
    # Then find successor... (more complex logic)

Why this is problematic:

  • Requires two separate traversals
  • More complex logic to handle different cases after finding p
  • Doesn't leverage the BST property efficiently

Solution: The correct approach directly searches for the successor by comparing values with p.val, not by finding p first. We only need p.val to make comparisons, not the actual node position.

Pitfall 2: Incorrect Handling of Equal Values

Some implementations incorrectly handle the case when root.val == p.val:

Incorrect Code:

while root:
    if root.val >= p.val:  # Wrong: includes equal case
        successor = root
        root = root.left
    else:
        root = root.right

Why this fails:

  • When root.val == p.val, we save the current node as successor
  • This would return p itself as its own successor, which is wrong
  • The successor must be strictly greater than p.val

Solution: Always use strict inequality (root.val > p.val) when checking for potential successors. Nodes with values equal to p.val should be treated the same as nodes with smaller values - by moving to the right subtree.

Pitfall 3: Forgetting to Update the Answer Variable

A subtle bug occurs when developers forget to save the potential successor:

Incorrect Code:

while root:
    if root.val > p.val:
        # Forgot to save: ans = root
        root = root.left
    else:
        root = root.right
return ans  # Always returns None

Why this fails:

  • Without saving ans = root, we lose track of valid successors
  • The function would always return None even when a successor exists

Solution: Always update the answer variable before moving to the left subtree. This ensures we keep track of the smallest valid successor found so far.

Pitfall 4: Misunderstanding When to Go Left vs Right

Some might incorrectly think they should always go right to find a larger value:

Incorrect Logic:

if root.val > p.val:
    ans = root
    root = root.right  # Wrong: should go left to find smaller successor

Why this fails:

  • Once we find a node greater than p.val, we need the smallest such node
  • Going right would find larger values, not the immediate successor
  • This could skip over the actual successor

Solution: Remember the goal: find the smallest value that is still greater than p.val. When you find a candidate (value > p.val), explore left to see if there's a smaller valid candidate. Only go right when the current value is not large enough (≤ p.val).

Discover Your Strengths and Weaknesses: Take Our 3-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