Most Frequent Subtree Sum
MedPrompt
Given the root of a binary tree, return the most frequent subtree sum. If there is a tie, return all the values with the highest frequency in any order.
The subtree sum of a node is defined as the sum of all the node values formed by the subtree rooted at that node (including the node itself).
Example 1:
Input: root = [5,2,-3]
Output: [2,-3,4]Example 2:
Input: root = [5,2,-5]
Output: [2]
Constraints:
- The number of nodes in the tree is in the range
[1, 104]. -105 <= Node.val <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves iterating through every node in the tree. For each node, a separate traversal is initiated to calculate the sum of its subtree. The frequencies of these sums are stored in a hash map. This method is straightforward but inefficient due to repeated calculations.
Algorithm
-
- Define a helper function
getAllNodesto traverse the tree (e.g., using pre-order traversal) and store all nodes in a list.
- Define a helper function
-
- Define another helper function
calculateSubtreeSumthat takes a node and recursively calculates the sum of all nodes in its subtree.
- Define another helper function
-
- In the main function, first call
getAllNodesto populate a list of all tree nodes.
- In the main function, first call
-
- Initialize a
HashMap<Integer, Integer>to store the frequency of each subtree sum and an integermaxFreqto track the maximum frequency found.
- Initialize a
-
- Iterate through each node in the list of all nodes.
-
- For each node, call
calculateSubtreeSumto get its subtree sum.
- For each node, call
-
- Update the frequency of this sum in the hash map and update
maxFreqif necessary.
- Update the frequency of this sum in the hash map and update
-
- After iterating through all nodes, create a result list.
-
- Iterate through the hash map and add any sum whose frequency equals
maxFreqto the result list.
- Iterate through the hash map and add any sum whose frequency equals
-
- Convert the result list to an integer array and return it.
Walkthrough
The core idea of this brute-force method is to separate the problem into two main parts: identifying all subtrees and then calculating the sum for each one. First, we perform a full traversal of the tree to get a list of all nodes. Each node in this list is the root of a subtree. Then, we iterate through this list. For each node, we perform a second, independent traversal starting from that node to compute its subtree sum. We use a hash map to keep track of how many times each sum occurs. After calculating all subtree sums, we find the highest frequency and collect all sums that have this frequency.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public int[] findFrequentTreeSum(TreeNode root) { if (root == null) { return new int[0]; } List<TreeNode> nodes = new ArrayList<>(); getAllNodes(root, nodes); Map<Integer, Integer> freqMap = new HashMap<>(); int maxFreq = 0; for (TreeNode node : nodes) { int sum = calculateSubtreeSum(node); int currentFreq = freqMap.getOrDefault(sum, 0) + 1; freqMap.put(sum, currentFreq); maxFreq = Math.max(maxFreq, currentFreq); } List<Integer> resultList = new ArrayList<>(); for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) { if (entry.getValue() == maxFreq) { resultList.add(entry.getKey()); } } return resultList.stream().mapToInt(i -> i).toArray(); } private void getAllNodes(TreeNode node, List<TreeNode> nodes) { if (node == null) { return; } nodes.add(node); getAllNodes(node.left, nodes); getAllNodes(node.right, nodes); } private int calculateSubtreeSum(TreeNode node) { if (node == null) { return 0; } return node.val + calculateSubtreeSum(node.left) + calculateSubtreeSum(node.right); }}Complexity
Time
O(N^2), where N is the number of nodes. The initial traversal to get all nodes is O(N). Then, for each of the N nodes, we perform a subtree sum calculation which can take up to O(N) time in the worst case (a skewed tree). This leads to a total time complexity dominated by N * O(N) = O(N^2).
Space
O(N), where N is the number of nodes. We need O(N) space to store the list of all nodes and up to O(N) for the hash map. The recursion stack for the sum calculation can also go up to O(N) in the worst case of a skewed tree.
Trade-offs
Pros
Conceptually straightforward and easy to break down into smaller, distinct problems.
Cons
Highly inefficient due to redundant computations. The value of a node is included in the sum calculation for itself and for every one of its ancestors, leading to repeated work.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { private Map < Integer , Integer > counter ; private int mx ; public int [] findFrequentTreeSum ( TreeNode root ) { counter = new HashMap <>(); mx = Integer . MIN_VALUE ; dfs ( root ); List < Integer > res = new ArrayList <>(); for ( Map . Entry < Integer , Integer > entry : counter . entrySet ()) { if ( entry . getValue () == mx ) { res . add ( entry . getKey ()); } } int [] ans = new int [ res . size ()]; for ( int i = 0 ; i < res . size (); ++ i ) { ans [ i ] = res . get ( i ); } return ans ; } private int dfs ( TreeNode root ) { if ( root == null ) { return 0 ; } int s = root . val + dfs ( root . left ) + dfs ( root . right ); counter . put ( s , counter . getOrDefault ( s , 0 ) + 1 ); mx = Math . max ( mx , counter . get ( s )); return s ; } }Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.