Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1120. Maximum Average Subtree - Leetcode Solution

Code Implementation

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def maximumAverageSubtree(self, root: Optional[TreeNode]) -> float:
        self.max_avg = 0

        def dfs(node):
            if not node:
                return (0, 0)  # sum, count
            left_sum, left_count = dfs(node.left)
            right_sum, right_count = dfs(node.right)
            curr_sum = node.val + left_sum + right_sum
            curr_count = 1 + left_count + right_count
            curr_avg = curr_sum / curr_count
            self.max_avg = max(self.max_avg, curr_avg)
            return (curr_sum, curr_count)

        dfs(root)
        return self.max_avg
      
// Definition for a binary tree node.
// struct TreeNode {
//     int val;
//     TreeNode *left;
//     TreeNode *right;
//     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
// };

class Solution {
public:
    double maxAvg = 0;
    pair dfs(TreeNode* node) {
        if (!node) return {0, 0};
        auto left = dfs(node->left);
        auto right = dfs(node->right);
        int currSum = node->val + left.first + right.first;
        int currCount = 1 + left.second + right.second;
        double currAvg = (double)currSum / currCount;
        maxAvg = max(maxAvg, currAvg);
        return {currSum, currCount};
    }
    double maximumAverageSubtree(TreeNode* root) {
        dfs(root);
        return maxAvg;
    }
};
      
// Definition for a binary tree node.
// public class TreeNode {
//     int val;
//     TreeNode left;
//     TreeNode right;
//     TreeNode(int x) { val = x; }
// }

class Solution {
    private double maxAvg = 0;
    public double maximumAverageSubtree(TreeNode root) {
        dfs(root);
        return maxAvg;
    }
    private int[] dfs(TreeNode node) {
        if (node == null) return new int[]{0, 0}; // sum, count
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        int currSum = node.val + left[0] + right[0];
        int currCount = 1 + left[1] + right[1];
        double currAvg = (double)currSum / currCount;
        maxAvg = Math.max(maxAvg, currAvg);
        return new int[]{currSum, currCount};
    }
}
      
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

var maximumAverageSubtree = function(root) {
    let maxAvg = 0;
    function dfs(node) {
        if (!node) return [0, 0]; // sum, count
        let [leftSum, leftCount] = dfs(node.left);
        let [rightSum, rightCount] = dfs(node.right);
        let currSum = node.val + leftSum + rightSum;
        let currCount = 1 + leftCount + rightCount;
        let currAvg = currSum / currCount;
        maxAvg = Math.max(maxAvg, currAvg);
        return [currSum, currCount];
    }
    dfs(root);
    return maxAvg;
};
      

Problem Description

Given the root of a binary tree, the goal is to find the maximum average value among all possible subtrees of the tree. A subtree is defined as any node and all its descendants. The average of a subtree is the sum of all node values in that subtree divided by the number of nodes in it.

  • Each node contains an integer value.
  • You must consider the average for every possible subtree (including single nodes).
  • Return the highest average found, as a floating point value.
  • All node values are integers, but the result should be a floating-point number.
  • There is always at least one node in the tree.

root: The root node of the binary tree.

Thought Process

At first glance, the problem seems to require checking every possible subtree and calculating its average. A brute-force approach would be to, for each node, traverse its entire subtree and compute the sum and count of nodes, then evaluate the average. However, this would be inefficient, as the same nodes would be counted multiple times across overlapping subtrees.

To optimize, we can realize that a post-order traversal (processing children before the parent) allows us to compute the sum and count of nodes for each subtree efficiently, reusing results. For each node, if we know the sum and count of its left and right subtrees, we can quickly compute the sum and count for the subtree rooted at that node. This avoids redundant calculations and ensures we only traverse each node once.

The key insight is to use recursion to return both the sum and count for each subtree, and update the maximum average found so far at each step.

Solution Approach

  • Post-order Traversal: We use a depth-first post-order traversal to process each node after its children, ensuring we have the necessary data to compute the subtree's sum and count.
  • Recursive Helper Function: For each node, the helper function returns a tuple (or pair/array) containing:
    • The sum of all values in the subtree rooted at this node.
    • The count of nodes in this subtree.
  • Updating Maximum Average: At each node, we compute the average by dividing the current subtree sum by its count. We then update a global or external variable tracking the maximum average found so far.
  • Base Case: If the node is null (i.e., a leaf's child), we return (0, 0) since there's nothing to add.
  • Return Value: After traversing the entire tree, the stored maximum average is returned as the answer.

This approach ensures each node is visited exactly once, and all calculations for subtree sums and counts are done efficiently.

Example Walkthrough

Sample Input:

        5
       / \
      6   1
  
  • Node 6: Subtree sum = 6, count = 1, average = 6.0
  • Node 1: Subtree sum = 1, count = 1, average = 1.0
  • Node 5: Left (6,1), Right (1,1) → sum = 5+6+1=12, count = 1+1+1=3, average = 12/3=4.0

The maximum average among all subtrees is 6.0 (from the subtree rooted at node 6).

As the traversal proceeds:

  • Start at leaf nodes (6 and 1), record their averages.
  • Move up to root (5), combine sums and counts, compute average.
  • Compare all found averages and return the largest.

Time and Space Complexity

  • Brute-force Approach:
    • For each node, traverse its entire subtree to compute sum and count. For N nodes, this results in O(N^2) time complexity.
  • Optimized Approach (Post-order Traversal):
    • Each node is visited exactly once. At each visit, we do constant work (sum, count, average, and a comparison).
    • Thus, total time complexity is O(N), where N is the number of nodes.
    • Space complexity is O(H) due to the recursion stack, where H is the height of the tree. In the worst case (skewed tree), H = N; in the best case (balanced), H = log N.

Summary

The Maximum Average Subtree problem is efficiently solved by leveraging post-order traversal to compute subtree sums and counts recursively. By updating the maximum average found at each node, we avoid redundant computations and ensure an optimal O(N) solution. This approach is both elegant and practical, demonstrating the power of recursion and careful traversal in tree-based problems.