1902. Depth of BST Given Insertion Order 🔒
Problem Description
You need to construct a binary search tree (BST) by inserting elements one by one from a given array order
, then find the maximum depth of the resulting tree.
The array order
is a permutation of integers from 1
to n
, representing the sequence in which values are inserted into the BST. The first element order[0]
becomes the root of the tree. Each subsequent element is inserted following standard BST insertion rules:
- If the value is less than the current node, go to the left subtree
- If the value is greater than the current node, go to the right subtree
- Continue this process until finding an empty position, then insert the node there
The depth of the BST is the number of nodes along the longest path from the root to any leaf node.
For example, if order = [2, 1, 4, 3]
:
- Insert
2
as the root (depth 1) - Insert
1
as left child of2
(depth 2) - Insert
4
as right child of2
(depth 2) - Insert
3
as left child of4
(depth 3)
The resulting tree has a maximum depth of 3.
The solution uses a SortedDict
to efficiently track the depth of each inserted node. For each new value, it finds the closest smaller and larger values already in the tree. The new node will be a child of one of these adjacent nodes, specifically the one with greater depth. The depth of the new node is one more than its parent's depth. The algorithm maintains the maximum depth seen throughout all insertions.
Intuition
When inserting a new value into a BST, we need to find where it will be placed. The key insight is that a new node will always become a leaf node - it never gets inserted between existing nodes. This new leaf will be a child of some existing node.
But which existing node will be the parent? When we traverse the BST to insert value v
, we compare it with nodes along a path from root to a leaf. The last node we visit before reaching an empty spot becomes the parent. This parent node will be either:
- The largest value smaller than
v
(ifv
becomes its right child) - The smallest value larger than
v
(ifv
becomes its left child)
Here's the crucial observation: between these two adjacent values (the closest smaller and larger values), the new node v
will be attached to whichever one was inserted later (has greater depth). Why? Because if the smaller value has greater depth, it means we reached it by going through the larger value first, so v
becomes the right child of the smaller value. Conversely, if the larger value has greater depth, we reached it through the smaller value, so v
becomes the left child of the larger value.
This means we don't need to build the actual tree structure. We only need to:
- Keep track of all inserted values and their depths
- For each new value, find its immediate neighbors (closest smaller and larger values)
- The new value's depth is
1 + max(depth of smaller neighbor, depth of larger neighbor)
A SortedDict
is perfect for this because it maintains values in sorted order and allows efficient binary search to find neighbors. We initialize it with sentinel values 0
and inf
at depth 0 to handle edge cases when inserting the minimum or maximum values.
Learn more about Tree, Binary Search Tree and Binary Tree patterns.
Solution Approach
The solution uses a SortedDict
data structure to efficiently track inserted values and their corresponding depths without building the actual BST.
Initialization:
sd = SortedDict({0: 0, inf: 0, order[0]: 1})
- We initialize the
SortedDict
with sentinel values:0
andinf
both at depth 0 - These sentinels handle edge cases when inserting minimum or maximum values
- The root node
order[0]
is inserted with depth 1 - Variable
ans
tracks the maximum depth seen so far, initially 1
Main Algorithm:
For each subsequent value v
in order[1:]
:
-
Find the closest neighbors:
lower = sd.bisect_left(v) - 1 higher = lower + 1
bisect_left(v)
returns the index wherev
would be inserted to maintain sorted orderlower
is the index of the largest value smaller thanv
higher
is the index of the smallest value larger thanv
-
Calculate the depth of the new node:
depth = 1 + max(sd.values()[lower], sd.values()[higher])
- The new node will be a child of either the lower or higher neighbor
- It attaches to whichever neighbor has greater depth (was inserted more recently in that subtree)
- The new node's depth is one more than its parent's depth
-
Update the maximum depth and insert the new node:
ans = max(ans, depth) sd[v] = depth
- Update
ans
if we found a deeper node - Add the new value and its depth to the
SortedDict
- Update
Time Complexity: O(n log n)
where n
is the length of the order array. Each insertion into the SortedDict
takes O(log n)
time.
Space Complexity: O(n)
for storing all values and their depths in the SortedDict
.
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 trace through the algorithm with order = [3, 1, 2, 5, 4, 6]
.
Initial Setup:
SortedDict
:{0: 0, inf: 0, 3: 1}
- Root node 3 has depth 1
ans = 1
Insert 1:
- Find neighbors of 1: lower = 0 (depth 0), higher = 3 (depth 1)
- Depth of 1 = 1 + max(0, 1) = 2
- Node 1 becomes left child of 3
SortedDict
:{0: 0, 1: 2, 3: 1, inf: 0}
ans = 2
Insert 2:
- Find neighbors of 2: lower = 1 (depth 2), higher = 3 (depth 1)
- Depth of 2 = 1 + max(2, 1) = 3
- Node 2 becomes right child of 1
SortedDict
:{0: 0, 1: 2, 2: 3, 3: 1, inf: 0}
ans = 3
Insert 5:
- Find neighbors of 5: lower = 3 (depth 1), higher = inf (depth 0)
- Depth of 5 = 1 + max(1, 0) = 2
- Node 5 becomes right child of 3
SortedDict
:{0: 0, 1: 2, 2: 3, 3: 1, 5: 2, inf: 0}
ans = 3
Insert 4:
- Find neighbors of 4: lower = 3 (depth 1), higher = 5 (depth 2)
- Depth of 4 = 1 + max(1, 2) = 3
- Node 4 becomes left child of 5
SortedDict
:{0: 0, 1: 2, 2: 3, 3: 1, 4: 3, 5: 2, inf: 0}
ans = 3
Insert 6:
- Find neighbors of 6: lower = 5 (depth 2), higher = inf (depth 0)
- Depth of 6 = 1 + max(2, 0) = 3
- Node 6 becomes right child of 5
SortedDict
:{0: 0, 1: 2, 2: 3, 3: 1, 4: 3, 5: 2, 6: 3, inf: 0}
ans = 3
The resulting BST structure:
3 (depth 1) / \ 1 5 (depth 2) \ / \ 2 4 6 (depth 3)
Maximum depth = 3
The key insight: when inserting value 4, it finds neighbors 3 and 5. Since 5 has greater depth (2) than 3 (1), node 4 attaches to node 5 as its child. This happens because to reach position 4 in the actual BST, we must traverse through node 5, making 5 the parent of 4.
Solution Implementation
1from sortedcontainers import SortedDict
2from typing import List
3from math import inf
4
5class Solution:
6 def maxDepthBST(self, order: List[int]) -> int:
7 # Initialize sorted dictionary with sentinels at boundaries
8 # 0 and inf act as boundaries with depth 0
9 # First element of order starts at depth 1
10 sorted_dict = SortedDict({0: 0, inf: 0, order[0]: 1})
11
12 # Track maximum depth seen so far
13 max_depth = 1
14
15 # Process remaining elements in insertion order
16 for value in order[1:]:
17 # Find the index of the largest key smaller than current value
18 lower_index = sorted_dict.bisect_left(value) - 1
19 higher_index = lower_index + 1
20
21 # Get the actual depth values at these indices
22 # The new node's depth is 1 plus the maximum depth of its potential parents
23 depth = 1 + max(sorted_dict.values()[lower_index],
24 sorted_dict.values()[higher_index])
25
26 # Update maximum depth if current depth is larger
27 max_depth = max(max_depth, depth)
28
29 # Insert current value with its calculated depth
30 sorted_dict[value] = depth
31
32 return max_depth
33
1class Solution {
2 public int maxDepthBST(int[] order) {
3 // TreeMap to store node values as keys and their depths as values
4 // Automatically maintains sorted order of keys
5 TreeMap<Integer, Integer> nodeDepthMap = new TreeMap<>();
6
7 // Initialize boundaries with depth 0
8 // 0 acts as left boundary (smaller than any node value)
9 nodeDepthMap.put(0, 0);
10 // MAX_VALUE acts as right boundary (larger than any node value)
11 nodeDepthMap.put(Integer.MAX_VALUE, 0);
12
13 // Insert root node with depth 1
14 nodeDepthMap.put(order[0], 1);
15 int maxDepth = 1;
16
17 // Process remaining nodes in insertion order
18 for (int i = 1; i < order.length; i++) {
19 int currentValue = order[i];
20
21 // Find the closest smaller value (left parent candidate)
22 Map.Entry<Integer, Integer> leftParent = nodeDepthMap.lowerEntry(currentValue);
23 // Find the closest larger value (right parent candidate)
24 Map.Entry<Integer, Integer> rightParent = nodeDepthMap.higherEntry(currentValue);
25
26 // Current node's depth is 1 + depth of its actual parent
27 // The actual parent is the one with greater depth between left and right candidates
28 int currentDepth = 1 + Math.max(leftParent.getValue(), rightParent.getValue());
29
30 // Update maximum depth encountered so far
31 maxDepth = Math.max(maxDepth, currentDepth);
32
33 // Store current node with its calculated depth
34 nodeDepthMap.put(currentValue, currentDepth);
35 }
36
37 return maxDepth;
38 }
39}
40
1class Solution {
2public:
3 int maxDepthBST(vector<int>& order) {
4 // Map to store node values as keys and their depths as values
5 // Automatically maintains sorted order of keys
6 map<int, int> node_depth_map;
7
8 // Initialize boundaries with depth 0
9 // 0 acts as left boundary (smaller than any node value)
10 node_depth_map[0] = 0;
11 // INT_MAX acts as right boundary (larger than any node value)
12 node_depth_map[INT_MAX] = 0;
13
14 // Insert root node with depth 1
15 node_depth_map[order[0]] = 1;
16 int max_depth = 1;
17
18 // Process remaining nodes in insertion order
19 for (int i = 1; i < order.size(); i++) {
20 int current_value = order[i];
21
22 // Find the closest smaller value (left parent candidate)
23 // lower_bound returns iterator to first element >= current_value
24 // We need the element just before it
25 auto right_it = node_depth_map.lower_bound(current_value);
26 auto left_it = prev(right_it);
27
28 // left_it points to closest smaller value (left parent candidate)
29 // right_it points to closest larger value (right parent candidate)
30
31 // Current node's depth is 1 + depth of its actual parent
32 // The actual parent is the one with greater depth between left and right candidates
33 int current_depth = 1 + max(left_it->second, right_it->second);
34
35 // Update maximum depth encountered so far
36 max_depth = max(max_depth, current_depth);
37
38 // Store current node with its calculated depth
39 node_depth_map[current_value] = current_depth;
40 }
41
42 return max_depth;
43 }
44};
45
1function maxDepthBST(order: number[]): number {
2 // Map to store node values as keys and their depths as values
3 // We'll maintain this in sorted order manually
4 const nodeDepthMap: Map<number, number> = new Map();
5
6 // Initialize boundaries with depth 0
7 // 0 acts as left boundary (smaller than any node value)
8 nodeDepthMap.set(0, 0);
9 // MAX_VALUE acts as right boundary (larger than any node value)
10 nodeDepthMap.set(Number.MAX_VALUE, 0);
11
12 // Insert root node with depth 1
13 nodeDepthMap.set(order[0], 1);
14 let maxDepth: number = 1;
15
16 // Process remaining nodes in insertion order
17 for (let i = 1; i < order.length; i++) {
18 const currentValue: number = order[i];
19
20 // Find the closest smaller value (left parent candidate)
21 let leftParentKey: number = 0;
22 let leftParentDepth: number = 0;
23
24 // Find the closest larger value (right parent candidate)
25 let rightParentKey: number = Number.MAX_VALUE;
26 let rightParentDepth: number = 0;
27
28 // Iterate through all entries to find lower and higher entries
29 for (const [key, depth] of nodeDepthMap.entries()) {
30 if (key < currentValue && key > leftParentKey) {
31 // Found a closer smaller value
32 leftParentKey = key;
33 leftParentDepth = depth;
34 }
35 if (key > currentValue && key < rightParentKey) {
36 // Found a closer larger value
37 rightParentKey = key;
38 rightParentDepth = depth;
39 }
40 }
41
42 // Current node's depth is 1 + depth of its actual parent
43 // The actual parent is the one with greater depth between left and right candidates
44 const currentDepth: number = 1 + Math.max(leftParentDepth, rightParentDepth);
45
46 // Update maximum depth encountered so far
47 maxDepth = Math.max(maxDepth, currentDepth);
48
49 // Store current node with its calculated depth
50 nodeDepthMap.set(currentValue, currentDepth);
51 }
52
53 return maxDepth;
54}
55
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of the input array order
.
- The code iterates through the array once with a for loop that runs
n-1
times - Inside each iteration:
sd.bisect_left(v)
performs a binary search operation on the sorted dictionary, which takesO(log k)
wherek
is the current size of the dictionary (at mostn+2
elements)sd.values()[lower]
andsd.values()[higher]
access values by index, which in a SortedDict takesO(log k)
time each- Inserting a new key-value pair with
sd[v] = depth
takesO(log k)
time to maintain the sorted order
- Since we perform these operations for each element and the dictionary grows to size
O(n)
, the total time complexity isO(n log n)
Space Complexity: O(n)
where n
is the length of the input array order
.
- The SortedDict
sd
stores at mostn+2
key-value pairs (all elements from the input array plus the two sentinel values0
andinf
) - The variable
ans
and other local variables useO(1)
space - Therefore, the overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Assuming the Parent Node
Pitfall: A common mistake is thinking that the new node's parent is simply the node with the value closest to it (minimum absolute difference). This would lead to incorrect depth calculations.
Why it's wrong: In a BST, when inserting a new value, its parent is determined by the path taken during insertion, not just proximity. The parent must be either:
- The largest value smaller than the new value (if the new node becomes its right child)
- The smallest value larger than the new value (if the new node becomes its left child)
Correct approach: The parent is the one among these two candidates that was inserted more recently into that position (has greater depth).
2. Forgetting the Sentinel Values
Pitfall: Omitting the sentinel values 0
and inf
from the initial SortedDict setup:
# Incorrect initialization sorted_dict = SortedDict({order[0]: 1})
Why it fails: Without sentinels, when inserting extreme values:
- If inserting a value smaller than all existing values,
lower_index
would be -1 - If inserting a value larger than all existing values,
higher_index
would exceed the valid range - This causes IndexError when accessing
sorted_dict.values()[lower_index]
orsorted_dict.values()[higher_index]
Solution: Always initialize with sentinels:
sorted_dict = SortedDict({0: 0, inf: 0, order[0]: 1})
3. Using Regular Dictionary Instead of SortedDict
Pitfall: Attempting to use a regular Python dict
and manually finding neighbors:
# Incorrect approach depths = {order[0]: 1} for value in order[1:]: # Finding neighbors in O(n) time smaller = [k for k in depths if k < value] larger = [k for k in depths if k > value] # ... rest of logic
Why it's inefficient: This approach has O(n²) time complexity because finding neighbors requires scanning all existing keys for each insertion.
Solution: Use SortedDict
which maintains keys in sorted order and provides O(log n) bisect operations.
4. Misunderstanding the Depth Calculation
Pitfall: Setting the new node's depth equal to its parent's depth instead of adding 1:
# Incorrect
depth = max(sorted_dict.values()[lower_index],
sorted_dict.values()[higher_index])
Correct version:
depth = 1 + max(sorted_dict.values()[lower_index],
sorted_dict.values()[higher_index])
The new node is one level deeper than its parent in the tree structure.
In a binary min heap, the minimum element can be found in:
Recommended Readings
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
Binary Search Tree Intro Definition A Binary Search Tree or BST is a rooted binary tree with the value of each internal node being greater than all the values in the respective node's left subtree and less than the ones in its right subtree An empty tree is a BST since it satisfies the
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!