Maximum Product of Splitted Binary Tree
MedPrompt
Given the root of a binary tree, split the binary tree into two subtrees by removing one edge such that the product of the sums of the subtrees is maximized.
Return the maximum product of the sums of the two subtrees. Since the answer may be too large, return it modulo 109 + 7.
Note that you need to maximize the answer before taking the mod and not after taking it.
Example 1:
Input: root = [1,2,3,4,5,6]
Output: 110
Explanation: Remove the red edge and get 2 binary trees with sum 11 and 10. Their product is 110 (11*10)Example 2:
Input: root = [1,null,2,3,4,null,null,5,6]
Output: 90
Explanation: Remove the red edge and get 2 binary trees with sum 15 and 6.Their product is 90 (15*6)
Constraints:
- The number of nodes in the tree is in the range
[2, 5 * 104]. 1 <= Node.val <= 104
Approaches
2 approaches with complexity analysis and trade-offs.
This naive approach considers every possible split one by one. A split is made by removing an edge. For each potential split, it traverses the two resulting new trees to calculate their respective sums and then computes their product. It keeps track of the maximum product found across all possible splits.
Algorithm
- First, perform a preliminary traversal (like DFS) to calculate the
totalSumof all nodes in the tree. This helps in easily finding the sum of the second part of any split. - Traverse the tree again. For each node
nin the tree (except the root), consider it as the root of a potential subtree to be split off. - To find the sum of the subtree rooted at
n, perform a full traversal starting fromn. Let's call thissubtreeSum. - The sum of the other part of the tree is simply
totalSum - subtreeSum. - Calculate the product:
product = subtreeSum * (totalSum - subtreeSum). - Keep a global
maxProductvariable and update it if the currentproductis larger. - After iterating through all possible nodes
nto form a split, themaxProductwill hold the maximum possible value. Return this value modulo 10^9 + 7.
Walkthrough
The algorithm works by identifying every edge in the tree and simulating its removal. A more structured way to do this is to iterate through every node, consider the subtree rooted at that node as one part of the split, and calculate the product. This involves a main traversal to select split points and nested traversals to calculate sums, leading to poor performance.
class Solution { long maxProduct = 0; long totalSum = 0; public int maxProduct(TreeNode root) { // Pre-calculate total sum to avoid one of the traversals inside the loop. totalSum = getSubtreeSum(root); // Traverse again to check every split. traverseAndCalculate(root, root); return (int) (maxProduct % 1000000007); } // Main traversal to iterate through each node as a potential split point. private void traverseAndCalculate(TreeNode node, TreeNode root) { if (node == null) { return; } // For each node, calculate its subtree sum (again). // This re-calculation is the source of inefficiency. long subtreeSum = getSubtreeSum(node); // We don't consider the case where the subtree is the whole tree, // as this corresponds to no split. if (node != root) { maxProduct = Math.max(maxProduct, subtreeSum * (totalSum - subtreeSum)); } traverseAndCalculate(node.left, root); traverseAndCalculate(node.right, root); } // Helper to calculate sum of a subtree starting from 'node'. private long getSubtreeSum(TreeNode node) { if (node == null) { return 0; } return node.val + getSubtreeSum(node.left) + getSubtreeSum(node.right); }}Complexity
Time
O(N^2), where N is the number of nodes. The `traverseAndCalculate` function visits N nodes. Inside it, `getSubtreeSum` is called, which may traverse up to N nodes in the worst case. This results in a quadratic time complexity.
Space
O(N), where N is the number of nodes. In the worst case of a skewed tree, the recursion stack for both the outer and inner traversals can go up to N levels deep.
Trade-offs
Pros
Conceptually straightforward as it directly simulates checking every possible split.
Cons
Highly inefficient due to the nested traversal structure, leading to repeated calculations of subtree sums.
This approach will likely exceed the time limit for the given constraints.
A highly efficient approach that avoids redundant computations by using two passes over the tree. The first pass calculates the total sum of all nodes. The second pass, a post-order traversal, calculates the sum of each subtree and simultaneously determines the maximum product without recalculating sums.
Algorithm
- Pass 1: Traverse the entire tree once (using DFS or BFS) to compute the
totalSumof all node values. It's crucial to use alongfor the sum to prevent potential overflow, as the total sum can exceed the capacity of a 32-bit integer. - Initialize a
longvariablemaxProduct = 0to store the maximum product found. - Pass 2: Perform a second, post-order DFS traversal. This traversal function will compute and return the sum of the subtree at the current node.
- Inside the post-order traversal for a node
n: a. Recursively find the sums of the left and right subtrees:leftSumandrightSum. b. Calculate the sum of the subtree rooted atn:subtreeSum = n.val + leftSum + rightSum. c. ThissubtreeSumrepresents the sum of one of the two trees if the edge connectingnto its parent is cut. The sum of the other tree istotalSum - subtreeSum. d. Calculate the product using 64-bit integers (long):product = subtreeSum * (totalSum - subtreeSum). e. Update the global maximum:maxProduct = Math.max(maxProduct, product). f. ReturnsubtreeSumto its parent caller, allowing the sums to be built up from the leaves to the root. - After the second pass is complete,
maxProductwill hold the maximum possible product. ReturnmaxProduct % (10^9 + 7).
Walkthrough
The key insight is that removing any edge partitions the tree into a specific subtree and "the rest of the tree". The sum of "the rest" can be found by subtracting the subtree's sum from the total sum of the original tree. We can find all possible subtree sums efficiently in a single post-order traversal. This avoids the O(N^2) complexity of the brute-force approach.
class Solution { long maxProduct = 0; public int maxProduct(TreeNode root) { // Pass 1: Calculate the total sum of the tree. long totalSum = findTotalSum(root); // Pass 2: Use a post-order traversal to find each subtree's sum // and calculate the product against the remaining part of the tree. findMaxProduct(root, totalSum); return (int) (maxProduct % 1000000007); } // Helper for Pass 1: Simple DFS to sum all nodes. private long findTotalSum(TreeNode node) { if (node == null) { return 0; } return node.val + findTotalSum(node.left) + findTotalSum(node.right); } // Helper for Pass 2: Post-order traversal that returns a subtree's sum // and updates the global maxProduct. private long findMaxProduct(TreeNode node, long totalSum) { if (node == null) { return 0; } // Post-order traversal: process children first. long leftSum = findMaxProduct(node.left, totalSum); long rightSum = findMaxProduct(node.right, totalSum); // Process current node. long subtreeSum = node.val + leftSum + rightSum; // Calculate product for the split defined by this subtree. // The split at the root (where subtreeSum == totalSum) gives a product of 0, // which is fine as it won't be the maximum. long product = subtreeSum * (totalSum - subtreeSum); maxProduct = Math.max(maxProduct, product); // Return this subtree's sum to the parent. return subtreeSum; }}Complexity
Time
O(N), where N is the number of nodes. The tree is traversed twice, and each traversal visits every node exactly once. The total time is O(N) + O(N) = O(N).
Space
O(H), where H is the height of the tree. This space is used by the recursion stack. In the worst case of a skewed tree, this becomes O(N).
Trade-offs
Pros
Optimal time complexity, making it very efficient for large trees.
Efficiently reuses computed information (subtree sums) through the post-order traversal mechanism, avoiding redundant work.
Cons
Requires two separate passes over the tree, although this does not affect the overall asymptotic complexity.
Uses a member variable (
maxProduct) to pass information up, which is a common pattern but can be less clean than pure functional approaches.
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 long ans ; private long s ; public int maxProduct ( TreeNode root ) { final int mod = ( int ) 1 e9 + 7 ; s = sum ( root ); dfs ( root ); return ( int ) ( ans % mod ); } private long dfs ( TreeNode root ) { if ( root == null ) { return 0 ; } long t = root . val + dfs ( root . left ) + dfs ( root . right ); if ( t < s ) { ans = Math . max ( ans , t * ( s - t )); } return t ; } private long sum ( TreeNode root ) { if ( root == null ) { return 0 ; } return root . val + sum ( root . left ) + sum ( root . right ); } }/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: int maxProduct ( TreeNode * root ) { using ll = long long ; ll ans = 0 ; const int mod = 1e9 + 7 ; function < ll ( TreeNode * ) > sum = [ & ]( TreeNode * root ) -> ll { if ( ! root ) { return 0 ; } return root -> val + sum ( root -> left ) + sum ( root -> right ); }; ll s = sum ( root ); function < ll ( TreeNode * ) > dfs = [ & ]( TreeNode * root ) -> ll { if ( ! root ) { return 0 ; } ll t = root -> val + dfs ( root -> left ) + dfs ( root -> right ); if ( t < s ) { ans = max ( ans , t * ( s - t )); } return t ; }; dfs ( root ); return ans % mod ; } };# 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 maxProduct ( self , root : Optional [ TreeNode ]) -> int : def sum ( root : Optional [ TreeNode ]) -> int : if root is None : return 0 return root . val + sum ( root . left ) + sum ( root . right ) def dfs ( root : Optional [ TreeNode ]) -> int : if root is None : return 0 t = root . val + dfs ( root . left ) + dfs ( root . right ) nonlocal ans , s if t < s : ans = max ( ans , t * ( s - t )) return t mod = 10 ** 9 + 7 s = sum ( root ) ans = 0 dfs ( root ) return ans % modVideo 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.