3831. Median of a Binary Search Tree Level 🔒
Problem Description
You are given the root of a Binary Search [Tree](/problems/tree_intro) (BST) and an integer level.
The root node is at level 0. Each level represents the distance from the root.
Return the median value of all node values present at the given level. If the level does not exist or contains no nodes, return -1.
The median is defined as the middle element after sorting the values at that level in non-decreasing order. If the number of values at that level is even, return the upper median (the larger of the two middle elements after sorting).
In other words, you are asked to look at every node whose distance from the root equals level, gather their values together, and find the median of those values:
- If there are no nodes at that level (for example, the tree is not deep enough to reach
level, orlevelis negative), return-1. - If there is an odd number of values, the median is the single middle value after sorting.
- If there is an even number of values, the median is the larger of the two central values (the upper median).
Because the input is a Binary Search Tree, performing an in-order traversal visits the node values in non-decreasing order. This means that the values collected at the target level during an in-order traversal are already sorted, so the median can be obtained directly by indexing into the middle of the collected list with nums[len(nums) // 2], which naturally yields the upper median for an even count.
How We Pick the Algorithm
Why BFS?
This problem maps to BFS through a short path in the full flowchart.
Collecting all node values at a specific tree level and finding their median requires BFS or DFS with level tracking.
Open in FlowchartShow step-by-step reasoning
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 kind of graph, made up of nodes (the tree nodes) connected by edges (the parent-child links).
Is it a tree?
- Yes: The input is explicitly the
rootof a Binary Search Tree, so we are clearly working with a tree structure.
DFS
- Yes: Once we identify the structure as a tree, the flowchart points directly to DFS. Here we use a depth-first in-order traversal, recursively visiting the left subtree, then the current node, then the right subtree. This lets us reach every node, track each node's level via a depth parameter, and collect the values located at the target
level. Because in-order traversal of a BST yields values in sorted order, the collected values are already non-decreasing, allowing the median to be read directly from the middle index.
Conclusion: The flowchart leads us through "graph → tree → DFS," confirming that the Depth-First Search pattern is the natural fit for traversing the BST and gathering the values at the requested level.
Intuition
The problem asks for the median of the values found at a specific level of the tree. The straightforward way to think about this is: gather all the values at that level, sort them, and pick the middle one.
The first key observation is how to know which level a node belongs to. If we traverse the tree and carry along a depth counter i, starting at 0 for the root and adding 1 each time we go one step deeper, then every node naturally knows its own level. Whenever i matches the target level, we record that node's value.
The second key observation comes from the fact that the input is a Binary Search Tree. In a BST, an in-order traversal (left subtree → current node → right subtree) visits the node values in non-decreasing order. This is a very handy property: if we collect the values at the target level during an in-order traversal, the values land in nums already sorted. We don't need to call a separate sort step at all.
Putting these two ideas together, we walk the tree using a depth-first in-order traversal. We pass down the current depth i, and only when i == level do we append the node's value to nums. Because of the BST in-order property, nums ends up sorted automatically.
Finally, we need the median. After sorting, the middle element sits at index len(nums) // 2:
- For an odd count, this is exactly the single middle element.
- For an even count, integer division lands on the larger of the two central elements, which is precisely the "upper median" the problem requests.
If nums is empty (the level doesn't exist or has no nodes), we simply return -1. This gives us the clean result nums[len(nums) // 2] if nums else -1.
Pattern Learn more about Tree, Depth-First Search, Breadth-First Search, Binary Search Tree and Binary Tree patterns.
Solution Approach
Solution 1: DFS
We notice that the problem requires us to find the median of node values at a certain level in a binary search tree. Since the definition of median is to sort the node values and take the middle value, and the in-order traversal of a binary search tree is inherently sorted, we can collect the node values at the specified level through in-order traversal.
We define a helper function dfs(root, i), where root is the current node and i is the level of the current node. In the function, the steps are as follows:
- If the current node
rootis empty, we return directly, since there is nothing to process. - Otherwise, we first recursively traverse the left subtree by calling
dfs(root.left, i + 1), increasing the depth by1. - Then we check whether the level of the current node equals the target level, i.e.,
i == level. If so, we append the current node's valueroot.valto the result listnums. - Finally, we recursively traverse the right subtree by calling
dfs(root.right, i + 1), again increasing the depth by1.
The order of these three steps (left → current → right) is exactly an in-order traversal. Because the tree is a binary search tree, this guarantees that the values we append to nums are added in non-decreasing order, so nums stays sorted without any extra sorting step.
We initialize an empty list nums to store the node values at the specified level, and call dfs(root, 0) to start the traversal from the root, whose level is 0.
After the traversal finishes, we determine the answer:
- If
numsis empty, the level does not exist or contains no nodes, so we return-1. - Otherwise, we return the middle value
nums[len(nums) // 2]. For an odd number of values this is the exact middle element, and for an even number of values the integer divisionlen(nums) // 2lands on the larger of the two central elements, giving the required upper median.
In terms of complexity, the time complexity is O(n), where n is the number of nodes in the tree, since we visit each node exactly once. The space complexity is O(n) in the worst case, accounting for the recursion stack depth and the values stored in nums.
Example Walkthrough
Consider the following BST and a target level = 2:
8 / \ 4 12 / \ / \ 2 6 10 14
We want the median of all node values at level = 2. By inspection, the nodes at level 2 are 2, 6, 10, 14 (the leaves), so their sorted values are [2, 6, 10, 14].
Setup. We create an empty list nums = [] and call dfs(root, 0), starting at node 8 with depth i = 0.
We follow the in-order pattern left → current → right, carrying the depth i down and adding 1 at each step. We only append a value when i == 2.
Let's trace the traversal:
dfs(8, 0)→ go left first todfs(4, 1).dfs(4, 1)→ go left first todfs(2, 2).dfs(2, 2)→ go left todfs(None, 3)(returns immediately). Now check current:i == 2, so append2.nums = [2]. Then go right todfs(None, 3)(returns).- Back in
dfs(4, 1)→ check current:i == 1 ≠ 2, skip. Go right todfs(6, 2). dfs(6, 2)→ left isNone. Check current:i == 2, so append6.nums = [2, 6]. Right isNone.- Back in
dfs(8, 0)→ check current:i == 0 ≠ 2, skip. Go right todfs(12, 1). dfs(12, 1)→ go left todfs(10, 2).dfs(10, 2)→ left isNone. Check current:i == 2, so append10.nums = [2, 6, 10]. Right isNone.- Back in
dfs(12, 1)→ check current:i == 1 ≠ 2, skip. Go right todfs(14, 2). dfs(14, 2)→ left isNone. Check current:i == 2, so append14.nums = [2, 6, 10, 14]. Right isNone.
Result of traversal. nums = [2, 6, 10, 14]. Notice it is already sorted in non-decreasing order — this happened automatically because the in-order traversal of a BST visits values in sorted order, so no explicit sort was needed.
Computing the median. The list has 4 values (an even count). We index with len(nums) // 2 = 4 // 2 = 2, giving nums[2] = 10. Since the count is even, this lands on the larger of the two central values (6 and 10), which is exactly the requested upper median.
So the answer is 10.
Edge note. If we had asked for level = 3 on this tree, no node sits that deep, so nums would stay empty and we would return -1.
Solution Implementation
1from typing import Optional, List
2
3
4# Definition for a binary tree node.
5# class TreeNode:
6# def __init__(self, val=0, left=None, right=None):
7# self.val = val
8# self.left = left
9# self.right = right
10class Solution:
11 def levelMedian(self, root: Optional[TreeNode], level: int) -> int:
12 def dfs(node: Optional[TreeNode], depth: int) -> None:
13 # Base case: stop when the node is empty
14 if node is None:
15 return
16 # Traverse the left subtree first (in-order traversal)
17 dfs(node.left, depth + 1)
18 # Collect the value if the current node is at the target level
19 if depth == level:
20 nums.append(node.val)
21 # Traverse the right subtree
22 dfs(node.right, depth + 1)
23
24 # Store all node values found at the target level (left-to-right order)
25 nums: List[int] = []
26 dfs(root, 0)
27 # Return the median value, or -1 if no nodes exist at the target level
28 return nums[len(nums) // 2] if nums else -1
291/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 // Collects the values of all nodes located at the target level.
18 // Because we traverse using in-order (left -> node -> right),
19 // the values are appended in left-to-right order for that level.
20 private List<Integer> levelValues = new ArrayList<>();
21
22 // The target depth whose median value we want to return.
23 private int targetLevel;
24
25 /**
26 * Returns the median value among all nodes located at the given level.
27 *
28 * @param root the root of the binary tree
29 * @param level the target level (root is at level 0)
30 * @return the median value at the target level, or -1 if no node exists there
31 */
32 public int levelMedian(TreeNode root, int level) {
33 this.targetLevel = level;
34 // Traverse the tree starting from depth 0.
35 dfs(root, 0);
36 // If no node was found at the target level, return -1.
37 // Otherwise return the middle element (left-to-right ordered).
38 return levelValues.isEmpty() ? -1 : levelValues.get(levelValues.size() / 2);
39 }
40
41 /**
42 * Performs an in-order depth-first search to gather node values at the target level.
43 *
44 * @param node the current node being visited
45 * @param depth the depth of the current node (root is at depth 0)
46 */
47 private void dfs(TreeNode node, int depth) {
48 // Base case: reached a null child, nothing to process.
49 if (node == null) {
50 return;
51 }
52 // Visit the left subtree first so values are collected left-to-right.
53 dfs(node.left, depth + 1);
54 // If the current node sits exactly at the target level, record its value.
55 if (depth == targetLevel) {
56 levelValues.add(node.val);
57 }
58 // Then visit the right subtree.
59 dfs(node.right, depth + 1);
60 }
61}
621/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 int levelMedian(TreeNode* root, int level) {
15 // Collect all node values located at the target level.
16 // Because we traverse in-order (left -> node -> right),
17 // the values are gathered in ascending order of their horizontal position,
18 // which conveniently mirrors the left-to-right order on that level.
19 std::vector<int> values;
20
21 // Recursive lambda performing a depth-first, in-order traversal.
22 // 'node' is the current node, 'depth' is its distance from the root.
23 auto dfs = [&](this auto&& dfs, TreeNode* node, int depth) -> void {
24 // Base case: a null node contributes nothing.
25 if (node == nullptr) {
26 return;
27 }
28
29 // Visit the left subtree first.
30 dfs(node->left, depth + 1);
31
32 // If the current node sits on the requested level, record its value.
33 if (depth == level) {
34 values.push_back(node->val);
35 }
36
37 // Finally visit the right subtree.
38 dfs(node->right, depth + 1);
39 };
40
41 // Kick off the traversal from the root at depth 0.
42 dfs(root, 0);
43
44 // If no node exists on the target level, return -1 as a sentinel.
45 // Otherwise, return the middle element (upper-median for even counts).
46 return values.empty() ? -1 : values[values.size() / 2];
47 }
48};
491/**
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 */
14function levelMedian(root: TreeNode | null, level: number): number {
15 // Stores the values of all nodes found at the target level,
16 // collected in left-to-right order due to the traversal scheme.
17 const values: number[] = [];
18
19 /**
20 * Depth-first traversal in the order: left subtree -> current node -> right subtree.
21 * @param node The current node being visited.
22 * @param depth The depth of the current node (root starts at 0).
23 */
24 const dfs = (node: TreeNode | null, depth: number): void => {
25 // Base case: nothing to do for a null node.
26 if (node === null) {
27 return;
28 }
29
30 // Traverse the left subtree first.
31 dfs(node.left, depth + 1);
32
33 // Record the value only when the current depth matches the target level.
34 if (depth === level) {
35 values.push(node.val);
36 }
37
38 // Traverse the right subtree last.
39 dfs(node.right, depth + 1);
40 };
41
42 // Kick off the traversal from the root at depth 0.
43 dfs(root, 0);
44
45 // If no node exists at the target level, return -1 as a sentinel value.
46 if (values.length === 0) {
47 return -1;
48 }
49
50 // Return the middle element (by collection index) at that level.
51 // `values.length >> 1` is an integer division by 2.
52 return values[values.length >> 1];
53}
54Time and Space Complexity
-
Time Complexity:
O(n), wherenis the number of nodes in the tree. Thedfsfunction performs an in-order traversal, visiting each node exactly once. At each node, the work done (comparingi == leveland possibly appending tonums) isO(1). Therefore, the total time spent is proportional to the number of nodes, givingO(n). -
Space Complexity:
O(n), wherenis the number of nodes in the tree. This comes from two sources:- The recursion call stack, which in the worst case (a completely skewed tree) can reach a depth of
O(n). - The
numslist, which stores all node values at the targetlevel. In the worst case, a single level can contain up toO(n)nodes.
Combining both, the overall space complexity is
O(n). - The recursion call stack, which in the worst case (a completely skewed tree) can reach a depth of
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Assuming in-order traversal yields sorted values at a single level
The solution's correctness hinges on a subtle but crucial property: in a BST, the in-order traversal visits ALL node values in non-decreasing order, including any subset of nodes restricted to one level.
A common misunderstanding is to assume that nodes at the same level are not guaranteed to be sorted by in-order traversal, leading developers to add an unnecessary nums.sort() call before indexing. While sorting still produces the correct answer, it is redundant and degrades the time complexity from O(n) to O(n + k log k) (where k is the number of nodes at the target level).
Why the in-order order is already sorted: In-order traversal globally orders every node value in non-decreasing order. If you filter that global sequence down to only the nodes at a fixed level, the relative order among those filtered values is preserved — and therefore they remain sorted. So no extra sorting is needed.
Incorrect (redundant) version:
nums.sort() # unnecessary — nums is already sorted
return nums[len(nums) // 2] if nums else -1
Correct version:
return nums[len(nums) // 2] if nums else -1
Pitfall 2: Picking the wrong median for an even count (lower vs. upper median)
The problem explicitly asks for the upper median (the larger of the two central values) when the level has an even number of nodes. A frequent mistake is to compute the lower median by using (len(nums) - 1) // 2 or by averaging the two middle values.
For an even-length list of size k, the two middle indices are k//2 - 1 (lower) and k//2 (upper). Using nums[len(nums) // 2] correctly lands on the upper median.
Length k | k // 2 | Element selected | Type |
|---|---|---|---|
4 ([1,2,3,4]) | 2 | 3 | upper median ✅ |
4 ([1,2,3,4]) | (4-1)//2 = 1 | 2 | lower median ❌ |
5 ([1,2,3,4,5]) | 2 | 3 | exact middle ✅ |
Incorrect:
return nums[(len(nums) - 1) // 2] if nums else -1 # gives lower median
Correct:
return nums[len(nums) // 2] if nums else -1 # gives upper median
Pitfall 3: Forgetting to handle missing levels and negative input
Two edge cases are easy to overlook:
levelexceeds tree depth — if the tree isn't deep enough, no node ever satisfiesdepth == level, leavingnumsempty.- Negative
level— sincedepthstarts at0and only increases, the conditiondepth == levelis never true for a negativelevel, sonumsstays empty.
In both situations nums ends up empty, and the guard if nums else -1 correctly returns -1. The pitfall is omitting this guard entirely, which causes an IndexError when indexing into an empty list:
Incorrect (crashes on empty level):
return nums[len(nums) // 2] # IndexError if nums is empty
Correct:
return nums[len(nums) // 2] if nums else -1
Pitfall 4: Inconsistent root-level convention (off-by-one)
The problem defines the root as level 0. A common error is to initialize the traversal with dfs(root, 1) or to increment depth before processing the root, which shifts every level by one. This causes the function to collect values from the wrong level entirely.
Always start with dfs(root, 0) so the root maps to level 0, the root's children to level 1, and so on.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapGiven a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___ in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
121public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
161function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16Recommended Readings
Tree Data Structure Explained 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
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!