A clear explanation of finding the inorder successor in a binary search tree when nodes contain parent pointers.
Problem Restatement
We are given a node in a binary search tree.
Each node contains:
| Field | Meaning |
|---|---|
val | Node value |
left | Left child |
right | Right child |
parent | Parent pointer |
We need to return the inorder successor of the given node.
The inorder successor of a node is the next node visited during inorder traversal:
left -> root -> rightIf no successor exists, return None.
The official problem provides parent pointers and asks for the next node in inorder traversal order. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | A node inside a BST |
| Output | The inorder successor node |
Return None | If no successor exists |
Function shape:
class Solution:
def inorderSuccessor(self, node: 'Node') -> 'Optional[Node]':
...Examples
Example 1:
5
/ \
3 6
/ \
2 4
/
1Suppose the input node is:
3The inorder traversal is:
1, 2, 3, 4, 5, 6The next node after 3 is:
4So the answer is node 4.
Example 2:
2
/ \
1 3If the input node is:
3then 3 is the final node in inorder traversal.
So the answer is:
NoneFirst Thought: Build the Entire Inorder Traversal
One possible solution is:
- Find the tree root.
- Perform full inorder traversal.
- Store nodes in an array.
- Locate the target node.
- Return the next array element.
This works, but it uses unnecessary extra memory.
The parent pointer gives us enough information to move upward directly.
Key Insight
There are only two cases.
Case 1: The node has a right subtree
If the node has a right child, then the successor is:
the leftmost node in the right subtreeExample:
5
\
8
/
6The successor of 5 is 6.
We move right once, then go left as far as possible.
Case 2: The node has no right subtree
Then we move upward using parent pointers.
We continue climbing while the current node is the right child of its parent.
The first parent where we arrive from the left side is the successor.
Example:
5
/
3
\
4The successor of 4 is 5.
We move upward from 4.
4 is a right child of 3, so continue upward.
3 is a left child of 5, so 5 is the successor.
Algorithm
If the node has a right child:
- Move to
node.right - Continue moving left until no left child exists
- Return that node
Otherwise:
- While:
node.parentexists- and
nodeis the right child of its parent
- Move upward
- Return
node.parent
If we reach the top without finding such a parent, return None.
Correctness
Inorder traversal visits nodes in this order:
left subtree -> node -> right subtreeIf a node has a right subtree, the next visited node must be the leftmost node inside that right subtree. No smaller node there can appear after the current node.
If a node does not have a right subtree, then its successor must lie among its ancestors.
Moving upward from a right child means the ancestor and its left subtree have already been fully visited, so traversal continues upward.
The first ancestor reached from the left side is the first node whose inorder visit occurs after the current node.
If no such ancestor exists, then the current node is the rightmost node in the BST and has no inorder successor.
Therefore the algorithm always returns the correct inorder successor or None.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(h) | At most one upward or downward path |
| Space | O(1) | No recursion or extra data structures |
Here:
h = tree heightImplementation
# Definition for a Node.
# class Node:
# def __init__(self, val):
# self.val = val
# self.left = None
# self.right = None
# self.parent = None
class Solution:
def inorderSuccessor(self, node: 'Node') -> 'Node':
# Case 1:
# Right subtree exists
if node.right:
current = node.right
while current.left:
current = current.left
return current
# Case 2:
# Move upward
while node.parent and node == node.parent.right:
node = node.parent
return node.parentCode Explanation
First check whether the node has a right subtree:
if node.right:If it does, move right once:
current = node.rightThen move left as far as possible:
while current.left:
current = current.leftThe leftmost node is the inorder successor.
If no right subtree exists, move upward:
while node.parent and node == node.parent.right:As long as the current node is a right child, its parent has already been visited during inorder traversal.
So continue climbing upward:
node = node.parentWhen the loop stops:
return node.parentreturns the first ancestor reached from the left side.
If no such ancestor exists, node.parent is None.
Testing
def connect(parent, left=None, right=None):
parent.left = left
parent.right = right
if left:
left.parent = parent
if right:
right.parent = parent
def test_inorder_successor():
s = Solution()
# Tree:
# 5
# / \
# 3 6
# / \
# 2 4
# /
# 1
n1 = Node(1)
n2 = Node(2)
n3 = Node(3)
n4 = Node(4)
n5 = Node(5)
n6 = Node(6)
connect(n5, n3, n6)
connect(n3, n2, n4)
connect(n2, n1)
assert s.inorderSuccessor(n3).val == 4
assert s.inorderSuccessor(n4).val == 5
assert s.inorderSuccessor(n1).val == 2
assert s.inorderSuccessor(n6) is None
print("all tests passed")Test meaning:
| Test | Why |
|---|---|
| Node with right subtree | Uses leftmost-right-subtree rule |
| Node without right subtree | Uses upward traversal |
| Small leftmost node | Simple successor case |
| Largest node | No successor exists |