Problem
A Cartesian tree with sequence S
is a binary tree defined by the following two properties:
- It is heap-ordered, so that each parent value is strictly less than that of its children.
- An in-order traversal of the tree produces nodes with values that correspond exactly to
S
.
For example, given the sequence [3, 2, 6, 1, 9]
, the resulting Cartesian tree would be:
|
|
Given a sequence S
, construct the corresponding Cartesian tree.
Examples
Example 1
|
|
Example 2
|
|
Example 3
|
|
Example 4
|
|
Example 5
|
|
Solution
Method 1 - Divide and Conquer Recursive
Intuition
The key insight is that in a Cartesian tree, the minimum element of any range becomes the root of that subtree. We can recursively apply this property: find the minimum element, make it the root, then recursively build left and right subtrees from the elements to the left and right of the minimum.
Approach
- Base case: if the sequence is empty, return null
- Find the minimum element and its index in the current range
- Create a new tree node with the minimum value as root
- Recursively build the left subtree from elements before the minimum index
- Recursively build the right subtree from elements after the minimum index
- Connect the left and right subtrees to the root
- Return the root node
Code
|
|
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(N²)
, where N is the length of the sequence. In worst case (sorted array), we scan entire array for minimum at each level - 🧺 Space complexity:
O(N)
, for recursion stack depth which can be up to N in worst case
Method 2 - Stack-based Linear Construction
Intuition
We can build the Cartesian tree in linear time using a stack-based approach. The key insight is to process elements from left to right, maintaining a stack of nodes where each node in the stack has a smaller value than the nodes below it. When we encounter a smaller element, we pop nodes from the stack and make them children appropriately.
Approach
- Initialize an empty stack to store TreeNode references
- For each element in the sequence from left to right:
- Create a new node with current element
- While stack is not empty and top element is greater than current:
- Pop the top node and make it left child of current node
- If stack is not empty after popping, make current node right child of stack top
- Push current node to stack
- The bottom element of stack is the root of Cartesian tree
- Return the root
Code
|
|
|
|
|
|
|
|
Complexity
- ⏰ Time complexity:
O(N)
, where N is the length of the sequence. Each element is pushed and popped at most once - 🧺 Space complexity:
O(N)
, for the stack which can contain up to N nodes
Method 3 - Segment Tree Based Construction
Intuition
We can use a segment tree approach where we preprocess the array to quickly find range minimum queries (RMQ). This allows us to find the minimum element in any range in O(log N) time, leading to better average performance for the divide and conquer approach.
Approach
- Build a segment tree for range minimum queries in O(N log N) time
- Use divide and conquer approach but leverage segment tree for O(log N) minimum finding
- For each recursive call, query segment tree to find minimum in current range
- Recursively build left and right subtrees based on minimum position
- This reduces the time complexity of finding minimum from O(N) to O(log N)
Code
|
|
Complexity
- ⏰ Time complexity:
O(N log N)
, where N is the length of the sequence. Building segment tree takes O(N log N) and each query takes O(log N) - 🧺 Space complexity:
O(N)
, for the segment tree and recursion stack
Notes
- Method 1 is intuitive but has O(N²) worst-case time complexity
- Method 2 is optimal with O(N) time complexity and is the preferred approach
- Method 3 provides a middle ground with O(N log N) complexity but more complex implementation
- The Cartesian tree is useful in applications like range minimum queries and treaps
- The stack-based approach exploits the property that we can build the tree in a single left-to-right pass
- In-order traversal of the constructed tree always yields the original sequence