501. Find Mode in Binary Search Tree
Problem Description
You are given the root of a binary search tree (BST) that may contain duplicate values. Your task is to find and return all the mode(s) of the tree. The mode is the value that appears most frequently in the tree.
Key points about the problem:
-
BST Definition: The tree follows BST properties where:
- The left subtree of any node contains values less than or equal to the node's value
- The right subtree of any node contains values greater than or equal to the node's value
- Both subtrees are also valid BSTs
-
Multiple Modes: If multiple values share the highest frequency, return all of them in any order
-
Return Format: Return the result as a list of integers containing all mode values
For example, if you have a BST with values [1, 2, 2, 3, 3]
where both 2 and 3 appear twice (the maximum frequency), you would return [2, 3]
.
The solution leverages the BST property by performing an in-order traversal, which visits nodes in sorted order. During traversal, it tracks:
cnt
: The count of the current value being processedprev
: The previous value seen (to detect when the value changes)mx
: The maximum frequency found so farans
: The list of mode values
When traversing in sorted order, identical values appear consecutively, making it easy to count frequencies. If a new maximum frequency is found, the answer list is reset with the current value. If the current frequency equals the maximum, the value is added to the existing answer list.
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 (left and right), 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 (BST), which is a specialized tree data structure with specific ordering properties.
DFS
- Since we answered "yes" to the tree question, the flowchart leads us directly to DFS (Depth-First Search).
Conclusion: The flowchart suggests using a DFS approach for this problem.
This makes perfect sense for finding modes in a BST because:
- DFS allows us to traverse the entire tree systematically to visit every node
- Using in-order DFS traversal on a BST gives us nodes in sorted order (left → root → right)
- When values are processed in sorted order, duplicate values appear consecutively, making it easy to count frequencies
- We can track the current count, maximum frequency, and mode values as we traverse
The solution implements this DFS strategy with an in-order traversal that maintains running counts and updates the mode list whenever a new maximum frequency is encountered or matched.
Intuition
The key insight comes from understanding the special property of BSTs: when we traverse a BST using in-order traversal (left → root → right), we visit nodes in sorted order. This is crucial because it transforms our problem into something much simpler.
Think about finding the mode in a sorted array like [1, 2, 2, 3, 3, 3, 4]
. We can simply iterate through it once, counting consecutive identical values. When the value changes, we know we've finished counting that particular number. This is much easier than dealing with an unsorted array where we'd need a hash map to track frequencies.
The same principle applies to our BST. By using in-order DFS traversal, we're essentially "flattening" the tree into a sorted sequence. As we traverse:
- We keep track of the previous value (
prev
) to know when we encounter a new distinct value - We maintain a running count (
cnt
) that increments when we see the same value again, or resets to 1 when we see a new value - We track the maximum frequency (
mx
) seen so far - We maintain our answer list (
ans
) of mode values
The beauty of this approach is that we only need O(1) extra space (excluding recursion stack) instead of using a hash map to count all frequencies. When we encounter a value with frequency equal to the current maximum, we add it to our answer list. If we find a value with even higher frequency, we clear the answer list and start fresh with this new mode.
This solution elegantly combines the BST property with DFS traversal to solve what could have been a more complex counting problem in a single pass through the tree.
Learn more about Tree, Depth-First Search, Binary Search Tree and Binary Tree patterns.
Solution Approach
The implementation uses an in-order DFS traversal with clever state tracking to find all modes in a single pass through the BST.
Core Algorithm Structure:
The solution defines a nested dfs
function that performs in-order traversal:
def dfs(root):
if root is None:
return
dfs(root.left) # Process left subtree
# Process current node
dfs(root.right) # Process right subtree
State Variables:
Before starting the traversal, we initialize four key variables:
prev = None
: Tracks the previous node's value to detect when values changecnt = 0
: Counts occurrences of the current valuemx = 0
: Stores the maximum frequency found so farans = []
: List to store all mode values
Processing Each Node:
When visiting each node in sorted order, the algorithm:
-
Updates the count:
cnt = cnt + 1 if prev == root.val else 1
If the current value equals the previous value, increment the count; otherwise, reset to 1.
-
Handles mode detection:
if cnt > mx: ans = [root.val] mx = cnt elif cnt == mx: ans.append(root.val)
- If current count exceeds the maximum, we found a new mode with higher frequency. Clear the answer list and add only this value.
- If current count equals the maximum, this value is also a mode. Add it to the existing modes.
-
Updates previous value:
prev = root.val
Store the current value for comparison with the next node.
Why This Works:
The in-order traversal ensures we process values in ascending order. For example, if the BST contains values [1, 2, 2, 3, 3, 3]
in sorted order:
- When we see the first
2
,cnt
becomes 1 - When we see the second
2
,cnt
becomes 2 - When we see the first
3
, we know we're done counting2
s, socnt
resets to 1 - This pattern continues, allowing us to accurately count frequencies
The use of nonlocal
keyword allows the nested function to modify the outer function's variables, maintaining state across recursive calls without passing parameters back and forth.
Time and Space Complexity:
- Time: O(n) where n is the number of nodes, as we visit each node exactly once
- Space: O(h) for the recursion stack, where h is the height of the tree
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate how the solution works.
Consider this BST:
4 / \ 2 6 / \ \ 2 3 6
The values in in-order traversal are: [2, 2, 3, 4, 6, 6]
Initial State:
prev = None
,cnt = 0
,mx = 0
,ans = []
Step-by-step traversal:
-
Visit first 2 (leftmost node)
prev = None
, so this is our first valuecnt = 1
(first occurrence)cnt > mx
(1 > 0), somx = 1
,ans = [2]
- Update:
prev = 2
-
Visit second 2
prev = 2
equals current value2
cnt = 2
(increment from 1)cnt > mx
(2 > 1), somx = 2
,ans = [2]
(reset with current value)- Update:
prev = 2
-
Visit 3
prev = 2
≠ current value3
cnt = 1
(reset for new value)cnt < mx
(1 < 2), no changes toans
- Update:
prev = 3
-
Visit 4 (root)
prev = 3
≠ current value4
cnt = 1
(reset for new value)cnt < mx
(1 < 2), no changes toans
- Update:
prev = 4
-
Visit first 6
prev = 4
≠ current value6
cnt = 1
(reset for new value)cnt < mx
(1 < 2), no changes toans
- Update:
prev = 6
-
Visit second 6
prev = 6
equals current value6
cnt = 2
(increment from 1)cnt == mx
(2 == 2), so append toans
:ans = [2, 6]
- Update:
prev = 6
Final Result: [2, 6]
- both values appear twice, which is the maximum frequency in the tree.
The key insight is that the in-order traversal gives us sorted values, allowing us to count consecutive occurrences efficiently. When a value changes (detected by comparing with prev
), we know we've finished counting that particular value and can reset our counter for the new value.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from typing import List, Optional
9
10class Solution:
11 def findMode(self, root: Optional[TreeNode]) -> List[int]:
12 """
13 Find the mode(s) in a Binary Search Tree.
14 Mode is the most frequently occurring value(s) in the tree.
15
16 Args:
17 root: Root node of the binary search tree
18
19 Returns:
20 List of integers representing the mode(s)
21 """
22
23 def inorder_traversal(node: Optional[TreeNode]) -> None:
24 """
25 Perform in-order traversal to process nodes in sorted order.
26 Updates the mode list based on frequency counting.
27
28 Args:
29 node: Current node being processed
30 """
31 if node is None:
32 return
33
34 # Process left subtree first (smaller values in BST)
35 inorder_traversal(node.left)
36
37 # Process current node
38 nonlocal max_frequency, previous_value, mode_list, current_count
39
40 # Update count: increment if same as previous, reset to 1 if different
41 if previous_value == node.val:
42 current_count += 1
43 else:
44 current_count = 1
45
46 # Update mode list based on current frequency
47 if current_count > max_frequency:
48 # Found new maximum frequency, reset mode list
49 mode_list = [node.val]
50 max_frequency = current_count
51 elif current_count == max_frequency:
52 # Found another value with same maximum frequency
53 mode_list.append(node.val)
54
55 # Update previous value for next comparison
56 previous_value = node.val
57
58 # Process right subtree (larger values in BST)
59 inorder_traversal(node.right)
60
61 # Initialize variables for tracking mode
62 previous_value = None # Previous node value in in-order traversal
63 max_frequency = 0 # Maximum frequency encountered so far
64 current_count = 0 # Count of current value sequence
65 mode_list = [] # List of mode values
66
67 # Start in-order traversal from root
68 inorder_traversal(root)
69
70 return mode_list
71
1/**
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 // Maximum frequency found so far
18 private int maxFrequency;
19
20 // Current count of consecutive equal values
21 private int currentCount;
22
23 // Previous node visited in the in-order traversal
24 private TreeNode previousNode;
25
26 // List to store the mode values
27 private List<Integer> modeList;
28
29 /**
30 * Finds all mode values in a binary search tree.
31 * Mode is the value that appears most frequently in the tree.
32 *
33 * @param root The root of the binary search tree
34 * @return An array containing all mode values
35 */
36 public int[] findMode(TreeNode root) {
37 // Initialize the result list
38 modeList = new ArrayList<>();
39
40 // Perform in-order traversal to find modes
41 inOrderTraversal(root);
42
43 // Convert list to array for return value
44 int[] result = new int[modeList.size()];
45 for (int i = 0; i < modeList.size(); i++) {
46 result[i] = modeList.get(i);
47 }
48
49 return result;
50 }
51
52 /**
53 * Performs in-order traversal of the BST to find mode values.
54 * In-order traversal of BST gives values in sorted order,
55 * making it easy to count consecutive equal values.
56 *
57 * @param node The current node being processed
58 */
59 private void inOrderTraversal(TreeNode node) {
60 // Base case: if node is null, return
61 if (node == null) {
62 return;
63 }
64
65 // Process left subtree first
66 inOrderTraversal(node.left);
67
68 // Process current node
69 // Update count: increment if same as previous, otherwise reset to 1
70 currentCount = (previousNode != null && previousNode.val == node.val)
71 ? currentCount + 1
72 : 1;
73
74 // Check if current count exceeds maximum frequency
75 if (currentCount > maxFrequency) {
76 // New maximum found, clear list and add current value
77 modeList = new ArrayList<>(Arrays.asList(node.val));
78 maxFrequency = currentCount;
79 } else if (currentCount == maxFrequency) {
80 // Current value has same frequency as maximum, add to list
81 modeList.add(node.val);
82 }
83
84 // Update previous node for next iteration
85 previousNode = node;
86
87 // Process right subtree
88 inOrderTraversal(node.right);
89 }
90}
91
1/**
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 vector<int> findMode(TreeNode* root) {
15 // Initialize member variables
16 previousNode = nullptr;
17 currentCount = 0;
18 maxCount = 0;
19 modes.clear();
20
21 // Perform in-order traversal to find modes
22 inorderTraversal(root);
23
24 return modes;
25 }
26
27private:
28 TreeNode* previousNode; // Pointer to the previously visited node in in-order traversal
29 int currentCount; // Count of consecutive occurrences of current value
30 int maxCount; // Maximum count found so far
31 vector<int> modes; // Vector to store the mode(s) of the BST
32
33 void inorderTraversal(TreeNode* node) {
34 // Base case: if node is null, return
35 if (!node) {
36 return;
37 }
38
39 // Traverse left subtree
40 inorderTraversal(node->left);
41
42 // Process current node
43 // If previous node exists and has same value, increment count; otherwise reset to 1
44 currentCount = (previousNode != nullptr && previousNode->val == node->val)
45 ? currentCount + 1
46 : 1;
47
48 // Update modes based on current count
49 if (currentCount > maxCount) {
50 // Found a new maximum, clear previous modes and add current value
51 modes.clear();
52 modes.push_back(node->val);
53 maxCount = currentCount;
54 } else if (currentCount == maxCount) {
55 // Current value has same frequency as maximum, add to modes
56 modes.push_back(node->val);
57 }
58
59 // Update previous node for next iteration
60 previousNode = node;
61
62 // Traverse right subtree
63 inorderTraversal(node->right);
64 }
65};
66
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8 constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
9 this.val = (val === undefined ? 0 : val);
10 this.left = (left === undefined ? null : left);
11 this.right = (right === undefined ? null : right);
12 }
13}
14
15// Global variables for tracking state during traversal
16let previousNode: TreeNode | null; // Pointer to the previously visited node in in-order traversal
17let currentCount: number; // Count of consecutive occurrences of current value
18let maxCount: number; // Maximum count found so far
19let modes: number[]; // Array to store the mode(s) of the BST
20
21/**
22 * Finds the mode(s) in a Binary Search Tree
23 * Mode is the value that appears most frequently in the BST
24 * @param root - The root node of the binary search tree
25 * @returns An array containing all mode values
26 */
27function findMode(root: TreeNode | null): number[] {
28 // Initialize global variables for fresh calculation
29 previousNode = null;
30 currentCount = 0;
31 maxCount = 0;
32 modes = [];
33
34 // Perform in-order traversal to find modes
35 // In-order traversal of BST visits nodes in sorted order
36 inorderTraversal(root);
37
38 return modes;
39}
40
41/**
42 * Performs in-order traversal of the BST and identifies modes
43 * Processes nodes in ascending order due to BST property
44 * @param node - Current node being processed
45 */
46function inorderTraversal(node: TreeNode | null): void {
47 // Base case: if node is null, return
48 if (!node) {
49 return;
50 }
51
52 // Traverse left subtree first (smaller values in BST)
53 inorderTraversal(node.left);
54
55 // Process current node
56 // Check if current value is same as previous (consecutive in sorted order)
57 if (previousNode !== null && previousNode.val === node.val) {
58 // Same value as previous, increment count
59 currentCount = currentCount + 1;
60 } else {
61 // Different value or first node, reset count to 1
62 currentCount = 1;
63 }
64
65 // Update modes based on current count
66 if (currentCount > maxCount) {
67 // Found a new maximum frequency
68 // Clear previous modes and add current value as new mode
69 modes = [];
70 modes.push(node.val);
71 maxCount = currentCount;
72 } else if (currentCount === maxCount) {
73 // Current value has same frequency as maximum
74 // Add to existing modes (multiple modes with same frequency)
75 modes.push(node.val);
76 }
77
78 // Update previous node reference for next iteration
79 previousNode = node;
80
81 // Traverse right subtree (larger values in BST)
82 inorderTraversal(node.right);
83}
84
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree. The algorithm performs an in-order traversal of the tree, visiting each node exactly once. The in-order traversal through the recursive DFS function processes every node with constant time operations (comparisons, assignments, and list operations), resulting in linear time complexity.
Space Complexity: O(h)
where h
is the height of the binary tree, which represents the recursive call stack space. In the worst case of a skewed tree, this would be O(n)
, while in the best case of a balanced tree, it would be O(log n)
. Additionally, the algorithm uses O(1)
auxiliary space for the variables prev
, mx
, cnt
, and the output list ans
could contain at most n
elements in the worst case where all nodes have the same value. Therefore, the overall space complexity is O(h) + O(k)
where k
is the number of modes, but since k ≤ n
, the space complexity can be expressed as O(h)
for the recursion stack plus O(n)
for the output in the worst case.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle the First Node Properly
A common mistake is not properly initializing or handling the comparison when previous_value
is None
(when processing the very first node in the traversal).
Incorrect approach:
# This could cause issues if not handled properly if previous_value == node.val: # When previous_value is None, this is always False current_count += 1 else: current_count = 1
Why it's actually fine in this solution:
The current implementation cleverly handles this because when previous_value
is None
, the comparison None == node.val
is always False
, so current_count
correctly gets set to 1 for the first node. However, this implicit behavior can be confusing.
More explicit solution:
if previous_value is not None and previous_value == node.val: current_count += 1 else: current_count = 1
2. Using Global Variables Instead of Nonlocal
Developers might try to use global variables or instance variables instead of the nonlocal
keyword, leading to scope issues.
Problematic approach:
class Solution:
def findMode(self, root):
self.previous_value = None # Instance variable
self.current_count = 0
# ... rest of the code
def inorder_traversal(node):
# Forgetting to use self.previous_value consistently
if previous_value == node.val: # NameError!
current_count += 1
Solution:
Stick with the nonlocal
approach for cleaner code, or consistently use self.
prefix for all instance variables.
3. Incorrect Mode List Reset Logic
A subtle bug can occur if the mode list isn't properly reset when finding a new maximum frequency.
Incorrect approach:
if current_count > max_frequency: mode_list.append(node.val) # Wrong! Should reset the list max_frequency = current_count elif current_count == max_frequency: mode_list.append(node.val)
This would keep values from lower frequencies in the result.
Correct approach:
if current_count > max_frequency: mode_list = [node.val] # Reset the list with only the new mode max_frequency = current_count elif current_count == max_frequency: mode_list.append(node.val)
4. Attempting Two-Pass Solution When One Pass Suffices
Some developers might overcomplicate by first traversing to find the maximum frequency, then traversing again to collect all values with that frequency.
Overcomplicated approach:
def findMode(self, root):
# First pass: find maximum frequency
frequency_map = {}
def count_frequencies(node):
if not node:
return
frequency_map[node.val] = frequency_map.get(node.val, 0) + 1
count_frequencies(node.left)
count_frequencies(node.right)
count_frequencies(root)
max_freq = max(frequency_map.values())
# Second pass: collect modes
return [val for val, freq in frequency_map.items() if freq == max_freq]
While this works, it uses O(n) extra space for the hash map and doesn't leverage the BST property. The single-pass in-order traversal is more elegant and space-efficient.
5. Not Leveraging BST Properties
Using a generic tree traversal without considering that in-order traversal of a BST gives sorted values.
Less optimal approach:
def findMode(self, root):
frequency_map = {}
def traverse(node):
if not node:
return
# Any order traversal, then using a hash map
frequency_map[node.val] = frequency_map.get(node.val, 0) + 1
traverse(node.left)
traverse(node.right)
traverse(root)
# Find max frequency and return modes
This misses the optimization opportunity that consecutive equal values in in-order traversal can be counted without extra space for frequency storage.
Which of the following problems can be solved with backtracking (select multiple)
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
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!