124. Binary Tree Maximum Path Sum
Problem Description
You are given a binary tree and need to find the maximum path sum among all possible paths in the tree.
A path is defined as a sequence of connected nodes where:
- Each pair of adjacent nodes in the sequence has an edge connecting them
- Each node can appear in the path at most once
- The path does not need to pass through the root node
The path sum is simply the sum of all node values in the path.
Your task is to find the maximum possible sum among all non-empty paths in the binary tree.
For example, consider a binary tree with nodes containing both positive and negative values. You need to find the path (which could go through any nodes as long as they're connected) that gives you the highest sum when you add up all the node values in that path.
The path can:
- Start and end at any nodes in the tree
- Go through parent-child relationships
- Form a "bent" path that goes up through a parent and down to another child
Given the root of the binary tree, return the maximum path sum value as an integer.
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 tree is a special type of graph where each node has at most two children and there are no cycles.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure.
DFS
- Yes: Following the flowchart, when we have a tree structure, the flowchart directly points us to use DFS (Depth-First Search).
Conclusion: The flowchart suggests using a DFS approach for solving the Binary Tree Maximum Path Sum problem.
This makes perfect sense because:
- We need to explore all possible paths in the tree to find the maximum sum
- DFS allows us to traverse the tree recursively, visiting each node exactly once
- At each node, we can calculate the maximum path sum that passes through that node
- We can maintain a global maximum while exploring all nodes via DFS
- The recursive nature of DFS perfectly matches the recursive structure of trees, making it ideal for computing values that depend on subtree results
Intuition
When we think about finding the maximum path sum in a binary tree, we need to consider what a path actually looks like. A path can start from any node, go up through some ancestors, and then go down to any descendant. The key insight is that every path has a "highest point" - a node that serves as the peak where the path changes direction from going up to going down (or just stays at that node).
For each node in the tree, we can ask: "What if this node is the highest point of the maximum path?" If a node is the highest point, then the maximum path through it would consist of:
- The node's own value
- Potentially a path going down through its left subtree
- Potentially a path going down through its right subtree
This gives us the formula: node.val + left_path + right_path
But here's the catch - when we're calculating what a node can contribute to its parent, we can only choose one direction to go down (either left or right), not both. Why? Because if we used both directions, we'd create a "forked" path that can't connect properly to the parent.
So we need to track two different values:
- The maximum path sum that uses this node as the highest point (can use both children)
- The maximum path sum this node can contribute to its parent (can only use one child)
Another crucial insight: if a subtree's contribution is negative, we're better off not including it at all. We can simply treat negative contributions as 0
since adding a negative value would only decrease our sum.
This naturally leads to a DFS solution where:
- We recursively calculate the maximum contribution from left and right subtrees
- We update our global answer with the sum through the current node as the highest point
- We return to the parent what this node can maximally contribute (node value plus the better child path)
Solution Approach
Following the DFS approach identified from our flowchart analysis, we implement a recursive solution that explores every node in the tree.
We design a helper function dfs(root)
that serves two purposes:
- It calculates the maximum path sum where the current node is the highest point
- It returns the maximum contribution this node can provide to its parent
The recursive function logic:
For the base case, if root
is None
, we return 0
since an empty subtree contributes nothing.
For each node, we recursively calculate the maximum contributions from its left and right subtrees:
left = max(0, dfs(root.left))
right = max(0, dfs(root.right))
Notice we use max(0, ...)
to ensure we never take negative contributions. If a subtree's best path sum is negative, we're better off not including it (treating it as 0
).
Next, we calculate the maximum path sum with the current node as the highest point:
ans = max(ans, root.val + left + right)
This represents a path that goes through the left subtree, up to the current node, and down through the right subtree. We update our global answer ans
if this path sum is larger than what we've seen before.
Finally, we return what this node can contribute to its parent:
return root.val + max(left, right)
We can only choose one direction (either left or right) when contributing to the parent, so we pick the maximum of the two.
Initialization and final result:
We initialize ans = -inf
to handle cases where all node values might be negative. Then we call dfs(root)
to explore the entire tree, and return the final answer which represents the maximum path sum found across all possible paths in the tree.
The time complexity is O(n)
where n
is the number of nodes, as we visit each node exactly once. The space complexity is O(h)
where h
is the height of the tree, due to the recursion stack.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach. Consider this binary tree:
-10 / \ 9 20 / \ 15 7
We'll use DFS to explore each node and track the maximum path sum.
Step 1: Start DFS from root (-10)
- First, we recursively process the left subtree (node 9)
Step 2: Process node 9 (leaf node)
- Left child is None, so
left = max(0, 0) = 0
- Right child is None, so
right = max(0, 0) = 0
- Maximum path with 9 as highest point:
9 + 0 + 0 = 9
- Update global answer:
ans = max(-inf, 9) = 9
- Return to parent:
9 + max(0, 0) = 9
Step 3: Process right subtree of root (node 20)
- First process its left child (node 15)
Step 4: Process node 15 (leaf node)
- Left and right children are None, so both contributions are 0
- Maximum path with 15 as highest point:
15 + 0 + 0 = 15
- Update global answer:
ans = max(9, 15) = 15
- Return to parent:
15 + max(0, 0) = 15
Step 5: Process node 7 (leaf node)
- Left and right children are None, so both contributions are 0
- Maximum path with 7 as highest point:
7 + 0 + 0 = 7
- Global answer stays:
ans = max(15, 7) = 15
- Return to parent:
7 + max(0, 0) = 7
Step 6: Back to node 20
- Left contribution from node 15:
left = max(0, 15) = 15
- Right contribution from node 7:
right = max(0, 7) = 7
- Maximum path with 20 as highest point:
20 + 15 + 7 = 42
- Update global answer:
ans = max(15, 42) = 42
- Return to parent:
20 + max(15, 7) = 35
Step 7: Back to root node (-10)
- Left contribution from node 9:
left = max(0, 9) = 9
- Right contribution from node 20:
right = max(0, 35) = 35
- Maximum path with -10 as highest point:
-10 + 9 + 35 = 34
- Global answer stays:
ans = max(42, 34) = 42
- Return (not used since this is root):
-10 + max(9, 35) = 25
Final Answer: 42
The maximum path sum is 42, which corresponds to the path: 15 â 20 â 7. This path uses node 20 as its highest point, going down to both children. Notice how we never included the root node (-10) in our optimal path because it would decrease the sum.
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 Optional
9
10class Solution:
11 def maxPathSum(self, root: Optional[TreeNode]) -> int:
12 """
13 Find the maximum path sum in a binary tree.
14 A path can start and end at any nodes in the tree.
15
16 Args:
17 root: Root node of the binary tree
18
19 Returns:
20 Maximum sum of any path in the tree
21 """
22
23 def calculate_max_gain(node: Optional[TreeNode]) -> int:
24 """
25 Calculate the maximum gain that can be obtained by including this node
26 in a path that goes through its parent.
27
28 Args:
29 node: Current node being processed
30
31 Returns:
32 Maximum gain from this node going upward (to parent)
33 """
34 # Base case: empty node contributes 0
35 if node is None:
36 return 0
37
38 # Recursively get maximum gain from left and right subtrees
39 # Use max(0, ...) to ignore negative paths (we can choose not to include them)
40 left_gain = max(0, calculate_max_gain(node.left))
41 right_gain = max(0, calculate_max_gain(node.right))
42
43 # Calculate the maximum path sum that passes through the current node
44 # This path includes: left subtree -> current node -> right subtree
45 current_max_path = node.val + left_gain + right_gain
46
47 # Update the global maximum path sum if current path is better
48 nonlocal max_sum
49 max_sum = max(max_sum, current_max_path)
50
51 # Return the maximum gain when continuing the path through parent
52 # We can only pick one branch (left or right) when going through parent
53 return node.val + max(left_gain, right_gain)
54
55 # Initialize the maximum sum to negative infinity to handle negative values
56 max_sum = float('-inf')
57
58 # Start the DFS traversal from root
59 calculate_max_gain(root)
60
61 return max_sum
62
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 // Global variable to track the maximum path sum found so far
18 // Initialize to -1001 since node values can be as low as -1000
19 private int maxSum = -1001;
20
21 /**
22 * Finds the maximum path sum in a binary tree.
23 * A path can start and end at any nodes in the tree.
24 *
25 * @param root The root of the binary tree
26 * @return The maximum sum of any path in the tree
27 */
28 public int maxPathSum(TreeNode root) {
29 calculateMaxGain(root);
30 return maxSum;
31 }
32
33 /**
34 * Helper method that calculates the maximum gain from each node.
35 * The gain represents the maximum sum of a path that starts from this node
36 * and goes down to one of its descendants (or just the node itself).
37 *
38 * @param node The current node being processed
39 * @return The maximum gain that can be obtained starting from this node going downward
40 */
41 private int calculateMaxGain(TreeNode node) {
42 // Base case: if node is null, contribution is 0
43 if (node == null) {
44 return 0;
45 }
46
47 // Recursively calculate the maximum gain from left subtree
48 // Use Math.max with 0 to ignore negative paths (we can choose not to include them)
49 int leftGain = Math.max(0, calculateMaxGain(node.left));
50
51 // Recursively calculate the maximum gain from right subtree
52 // Use Math.max with 0 to ignore negative paths
53 int rightGain = Math.max(0, calculateMaxGain(node.right));
54
55 // Calculate the maximum path sum that passes through the current node
56 // This path includes the current node, potentially the left path, and potentially the right path
57 int currentMaxPath = node.val + leftGain + rightGain;
58
59 // Update the global maximum if the current path sum is greater
60 maxSum = Math.max(maxSum, currentMaxPath);
61
62 // Return the maximum gain from this node to its parent
63 // Parent can only use one branch (either left or right) plus the current node's value
64 return node.val + Math.max(leftGain, rightGain);
65 }
66}
67
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 int maxPathSum(TreeNode* root) {
15 // Initialize the maximum path sum to the smallest possible value
16 // (constraints: node values are in range [-1000, 1000])
17 int maxSum = INT_MIN;
18
19 // Define a recursive function to calculate the maximum path sum
20 // Returns the maximum path sum starting from the current node and going down
21 function<int(TreeNode*)> calculateMaxPath = [&](TreeNode* node) -> int {
22 // Base case: if node is null, contribute 0 to the path
23 if (!node) {
24 return 0;
25 }
26
27 // Recursively get the maximum path sum from left and right subtrees
28 // Use max(0, ...) to ignore negative paths (we can choose not to include them)
29 int leftMaxPath = max(0, calculateMaxPath(node->left));
30 int rightMaxPath = max(0, calculateMaxPath(node->right));
31
32 // Calculate the maximum path sum that passes through the current node
33 // This path includes both left and right subtrees plus the current node value
34 int currentMaxPath = leftMaxPath + rightMaxPath + node->val;
35
36 // Update the global maximum if the current path is larger
37 maxSum = max(maxSum, currentMaxPath);
38
39 // Return the maximum path sum starting from this node going down
40 // We can only choose either left or right path (not both) when extending upward
41 return node->val + max(leftMaxPath, rightMaxPath);
42 };
43
44 // Start the DFS traversal from the root
45 calculateMaxPath(root);
46
47 // Return the maximum path sum found
48 return maxSum;
49 }
50};
51
1/**
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 */
14
15/**
16 * Finds the maximum path sum in a binary tree
17 * A path can start and end at any node in the tree
18 * @param root - The root node of the binary tree
19 * @returns The maximum sum of any path in the tree
20 */
21function maxPathSum(root: TreeNode | null): number {
22 // Initialize with minimum possible value (-1001 based on problem constraints)
23 let maxSum: number = -1001;
24
25 /**
26 * Performs depth-first search to calculate maximum path sum
27 * Returns the maximum sum of a path that goes through the current node
28 * and continues upward (can only use one branch when returning to parent)
29 * @param node - Current node being processed
30 * @returns Maximum sum of path from this node going upward
31 */
32 const calculateMaxPath = (node: TreeNode | null): number => {
33 // Base case: if node is null, contribute 0 to the path sum
34 if (!node) {
35 return 0;
36 }
37
38 // Recursively get maximum path sum from left subtree
39 // Use Math.max with 0 to ignore negative path sums
40 const leftMaxSum: number = Math.max(0, calculateMaxPath(node.left));
41
42 // Recursively get maximum path sum from right subtree
43 // Use Math.max with 0 to ignore negative path sums
44 const rightMaxSum: number = Math.max(0, calculateMaxPath(node.right));
45
46 // Calculate the maximum path sum that passes through current node
47 // This includes both left and right branches plus current node value
48 const currentPathSum: number = leftMaxSum + rightMaxSum + node.val;
49
50 // Update global maximum if current path sum is greater
51 maxSum = Math.max(maxSum, currentPathSum);
52
53 // Return the maximum sum path that can be extended to parent
54 // Can only choose one branch (left or right) when returning upward
55 return Math.max(leftMaxSum, rightMaxSum) + node.val;
56 };
57
58 // Start DFS traversal from root
59 calculateMaxPath(root);
60
61 // Return the maximum path sum found
62 return maxSum;
63}
64
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs a depth-first search (DFS) traversal of the binary tree. Each node is visited exactly once during the traversal. At each node, the algorithm performs constant time operations:
- Computing the maximum of left subtree result and 0:
O(1)
- Computing the maximum of right subtree result and 0:
O(1)
- Updating the global maximum
ans
:O(1)
- Returning the maximum path sum through the current node:
O(1)
Since we visit all n
nodes exactly once and perform O(1)
work at each node, the total time complexity is O(n)
.
Space Complexity: O(n)
The space complexity is determined by the recursive call stack used during DFS traversal. In the worst case, the binary tree is completely unbalanced (e.g., a linked list structure where each node has only one child), leading to a recursion depth of n
. This would require O(n)
space on the call stack.
In the best case of a perfectly balanced binary tree, the recursion depth would be O(log n)
, but since we consider worst-case complexity, the space complexity is O(n)
.
Additionally, the algorithm uses only a constant amount of extra space for variables (ans
, left
, right
), which doesn't affect the overall space complexity.
Common Pitfalls
Pitfall 1: Not Handling Negative Values Correctly
A frequent mistake is forgetting to use max(0, ...)
when calculating the contribution from child nodes. Without this check, negative path sums from subtrees would be forced into the calculation, potentially reducing the overall maximum.
Incorrect Implementation:
def calculate_max_gain(node):
if node is None:
return 0
# Wrong: Always including child contributions even if negative
left_gain = calculate_max_gain(node.left)
right_gain = calculate_max_gain(node.right)
current_max_path = node.val + left_gain + right_gain
max_sum = max(max_sum, current_max_path)
return node.val + max(left_gain, right_gain)
Why this fails: Consider a tree where a node has value 10 but its left child subtree has a maximum contribution of -20. Without the max(0, ...)
check, we'd be forced to include -20 in our path calculation, giving us a path sum of -10 instead of just 10.
Correct Implementation:
# Always use max(0, ...) to optionally exclude negative contributions
left_gain = max(0, calculate_max_gain(node.left))
right_gain = max(0, calculate_max_gain(node.right))
Pitfall 2: Confusing Path Sum vs. Node Contribution
Another common error is returning the wrong value from the recursive function. The function calculates two different values:
- The maximum path sum with current node as the highest point (stored in
max_sum
) - The maximum contribution this node can provide to its parent (the return value)
Incorrect Implementation:
def calculate_max_gain(node):
if node is None:
return 0
left_gain = max(0, calculate_max_gain(node.left))
right_gain = max(0, calculate_max_gain(node.right))
current_max_path = node.val + left_gain + right_gain
max_sum = max(max_sum, current_max_path)
# Wrong: Returning the full path sum instead of contribution to parent
return current_max_path
Why this fails: When we return current_max_path
, we're telling the parent node that it can use a path that goes through both children of the current node, which violates the tree structure. A path through the parent can only go down one branch, not both.
Correct Implementation:
# Return only what can be contributed upward (node value + one branch)
return node.val + max(left_gain, right_gain)
Pitfall 3: Incorrect Initialization of Maximum Sum
Initializing max_sum
to 0 instead of negative infinity can cause issues when all node values in the tree are negative.
Incorrect Implementation:
max_sum = 0 # Wrong: assumes at least one non-negative value exists
Example where this fails: A tree with a single node containing value -5 should return -5, but with max_sum = 0
, the algorithm would incorrectly return 0.
Correct Implementation:
max_sum = float('-inf') # Handles all negative values correctly
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Donât Miss This!