147. Binary Tree Maximum Path Sum
π LeetCode Problem: 124. Binary Tree Maximum Path Sum
π Difficulty: Hard
π·οΈ Topics: Binary Trees, DFS, Recursion, Global Max, Post-order
Problem Statement
A path in a binary tree is a sequence of nodes where each pair of adjacent nodes in the sequence has an edge connecting them. A node can only appear in the sequence at most once. Note that the path does not need to pass through the root.
The path sum of a path is the sum of the node's values in the path.
Given the root of a binary tree, return the maximum path sum of any non-empty path.
Example 1:
Input: root = [1,2,3]
Output: 6
Explanation: The optimal path is 2 -> 1 -> 3 with a path sum of 2 + 1 + 3 = 6.
Example 2:
Input: root = [-10,9,20,null,null,15,7]
Output: 42
Explanation: The optimal path is 15 -> 20 -> 7 with a path sum of 15 + 20 + 7 = 42.
Constraints:
- The number of nodes in the tree is in the range [1, 3 * 10^4]
- -1000 <= Node.val <= 1000
π³ Visual Understanding - What is a Path?
Path Definition:
A PATH is a sequence of connected nodes
- Each node appears AT MOST ONCE
- Path does NOT need to go through root
- Path can start and end ANYWHERE
Key insight: Path can be:
1. Single node
2. Going down (parent β child β grandchild)
3. Going through node (left β node β right)
4. Any combination!
Example 1 - Simple Tree:
Tree:
1
/ \
2 3
All possible paths:
1. [2] β sum = 2
2. [1] β sum = 1
3. [3] β sum = 3
4. [2, 1] β sum = 3
5. [1, 3] β sum = 4
6. [2, 1, 3] β sum = 6 β MAXIMUM! β
Maximum path sum: 6
Path: 2 β 1 β 3
Example 2 - With Negative Values:
Tree:
-10
/ \
9 20
/ \
15 7
Key paths:
1. [9] β sum = 9
2. [-10] β sum = -10
3. [20] β sum = 20
4. [15] β sum = 15
5. [7] β sum = 7
6. [15, 20] β sum = 35
7. [20, 7] β sum = 27
8. [15, 20, 7] β sum = 42 β MAXIMUM! β
9. [9, -10, 20] β sum = 19
10. [9, -10, 20, 15] β sum = 34
Maximum path sum: 42
Path: 15 β 20 β 7 (doesn't include -10!)
Visual Path Types:
Type 1: Single node
A
Path: [A]
Type 2: Straight down
A
/
B
/
C
Paths: [A], [A,B], [A,B,C]
Type 3: Through node (inverted V)
B
/ \
A C
Path: [A,B,C]
This is KEY! Path going THROUGH B
Type 4: L-shaped (one branch only)
A
/ \
B C
/
D
Path: [D,B,A] (not including C)
π§ The AHA Moment - What to Return vs What to Track
The Critical Insight:
TWO different concepts:
1. RETURN VALUE (to parent):
"Best path sum that can EXTEND UPWARD"
= node + max(left_path, right_path, 0)
Parent can extend this path!
2. GLOBAL MAX (tracked separately):
"Best path sum going THROUGH this node"
= node + left_path + right_path
This path is COMPLETE, can't extend!
This is the CORE of the problem! π
Visual Explanation:
Tree:
10
/ \
2 3
At node 10:
OPTION A: Path extending UP
?
|
10
/ \
2 3
Can include: 10 + max(2, 3) = 13
Return 13 (parent can extend)
OPTION B: Path THROUGH 10
10
/ \
2 3
Complete path: 2 + 10 + 3 = 15
Track in global max (can't extend!)
What to return? 13 (extendable)
What to track? 15 (complete path through node)
Why This Matters:
If we return the complete path:
10
/ \
2 3
/ \
5 7
At node 10:
Complete path through 10: 5+2+10+3+7 = 27
If we return 27 to parent:
20
/
10 (returns 27)
/ \
2 3
/ \
5 7
Parent calculates: 20 + 27 = 47
But this path is INVALID!
Would be: 5β2β10β3β7β20
Node 10 appears in middle with BOTH children! β
We must return EXTENDABLE paths only! π―
π§ Recursive Thinking - Building the Solution
Base Case:
What's the simplest case?
β null node
What to return?
β 0 (no path, contributes 0)
Why not Integer.MIN_VALUE?
β Because parent should be able to IGNORE this branch
β Returning 0 means "no contribution"
if (node == null) return 0;
Recursive Case - Step by Step:
At each node, we need to:
STEP 1: Get best paths from children
leftMax = maxPathSum(node.left)
rightMax = maxPathSum(node.right)
These are EXTENDABLE paths (or 0)
STEP 2: Consider IGNORING negative paths
leftMax = max(0, leftMax)
rightMax = max(0, rightMax)
Why? Negative paths DECREASE sum!
Better to not include them!
STEP 3: Calculate path THROUGH current node
pathThrough = node.val + leftMax + rightMax
This is a COMPLETE path (inverted V)
Update global max with this!
STEP 4: Return EXTENDABLE path
return node.val + max(leftMax, rightMax)
Parent can only extend ONE branch!
Choose the better one!
What Each Node Returns:
Each node returns:
"Best path sum starting at this node going DOWN"
This can be:
- Just the node itself
- Node + left path
- Node + right path
But NEVER node + both paths (not extendable!)
Example:
10
/ \
2 3
Node 2 returns: 2
Node 3 returns: 3
Node 10 returns: 10 + max(2, 3) = 13
But tracks globally: 2 + 10 + 3 = 15
π― Solution: DFS with Global Maximum
Implementation:
class Solution {
private int maxSum; // Global variable to track maximum
public int maxPathSum(TreeNode root) {
// Initialize with smallest possible value
// WHY? Any path will be larger than this
maxSum = Integer.MIN_VALUE;
// Start DFS
maxGain(root);
// Return the global maximum found
return maxSum;
}
private int maxGain(TreeNode node) {
// Base case: null node contributes 0
if (node == null) {
return 0;
}
// STEP 1: Get maximum gain from left subtree
// Recursively calculate best path in left
int leftGain = maxGain(node.left);
// STEP 2: Get maximum gain from right subtree
// Recursively calculate best path in right
int rightGain = maxGain(node.right);
// STEP 3: Ignore negative paths
// WHY? Negative paths decrease sum, better to exclude
// This means: "don't take this branch at all"
leftGain = Math.max(0, leftGain);
rightGain = Math.max(0, rightGain);
// STEP 4: Calculate path THROUGH current node
// This is: left_path + node + right_path
// This forms an "inverted V" or straight line
int pathThroughNode = node.val + leftGain + rightGain;
// STEP 5: Update global maximum
// This path might be the answer!
// WHY separate variable? Because this path can't extend to parent
maxSum = Math.max(maxSum, pathThroughNode);
// STEP 6: Return maximum gain TO PARENT
// Parent can only extend ONE branch (left OR right)
// WHY? If we include both, path would split at parent
// Choose the better branch!
return node.val + Math.max(leftGain, rightGain);
}
}
Why Global Variable?
Global maxSum tracks: "Best complete path found anywhere"
Can't return this because:
- Parent needs extendable path
- Complete path can't extend
Two separate concerns:
1. What parent needs (return value)
2. What might be answer (global max)
Global variable solves this elegantly! π―
π Detailed Dry Run - Complete Recursion
Tree:
-10
/ \
9 20
/ \
15 7
Complete Call Stack Visualization:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
INITIAL STATE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
maxSum = -β
Start: maxGain(-10)
βββββββββββββββββββββββββββββββββββββββββββββββββββ
LEVEL 0: maxGain(-10)
βββββββββββββββββββββββββββββββββββββββββββββββββββ
node = -10
ββ LEFT: maxGain(9) ββββββββββββββββββββββββββββββββ
β β
β LEVEL 1: maxGain(9) β
β β β
β ββ node = 9 β
β β β
β ββ LEFT: maxGain(null) β return 0 β
β ββ RIGHT: maxGain(null) β return 0 β
β β β
β ββ leftGain = max(0, 0) = 0 β
β ββ rightGain = max(0, 0) = 0 β
β β β
β ββ pathThrough = 9 + 0 + 0 = 9 β
β ββ maxSum = max(-β, 9) = 9 β
β β β
β ββ return 9 + max(0, 0) = 9 β
β β
ββ RIGHT: maxGain(20) ββββββββββββββββββββββββββββββ
β β
β LEVEL 1: maxGain(20) β
β β β
β ββ node = 20 β
β β β
β ββ LEFT: maxGain(15) βββββββββββββββββββββββ β
β β β β
β β LEVEL 2: maxGain(15) β β
β β β β β
β β ββ node = 15 β β
β β ββ LEFT: maxGain(null) β return 0 β β
β β ββ RIGHT: maxGain(null) β return 0 β β
β β β β β
β β ββ leftGain = 0, rightGain = 0 β β
β β ββ pathThrough = 15 + 0 + 0 = 15 β β
β β ββ maxSum = max(9, 15) = 15 β β
β β β β β
β β ββ return 15 + max(0, 0) = 15 β β
β β β β
β ββ RIGHT: maxGain(7) βββββββββββββββββββββββ β
β β ββ β
β β LEVEL 2: maxGain(7) ββ β
β β β ββ β
β β ββ node = 7 ββ β
β β ββ LEFT: maxGain(null) β return 0 ββ β
β β ββ RIGHT: maxGain(null) β return 0 ββ β
β β β ββ β
β β ββ leftGain = 0, rightGain = 0 ββ β
β β ββ pathThrough = 7 + 0 + 0 = 7 ββ β
β β ββ maxSum = max(15, 7) = 15 (no change)ββ β
β β β ββ β
β β ββ return 7 + max(0, 0) = 7 ββ β
β β ββ β
β ββ Back at node 20: β β
β ββ leftGain from 15 = 15 β β
β ββ rightGain from 7 = 7 β β
β β β β
β ββ leftGain = max(0, 15) = 15 β β
β ββ rightGain = max(0, 7) = 7 β β
β β β β
β ββ pathThrough = 20 + 15 + 7 = 42 β β
β ββ maxSum = max(15, 42) = 42 β NEW MAX! β β β
β β β β
β ββ return 20 + max(15, 7) = 35 β β
β β
ββ Back at node -10: β
ββ leftGain from 9 = 9 β
ββ rightGain from 20 = 35 β
β β
ββ leftGain = max(0, 9) = 9 β
ββ rightGain = max(0, 35) = 35 β
β β
ββ pathThrough = -10 + 9 + 35 = 34 β
ββ maxSum = max(42, 34) = 42 (no change) β
β β
ββ return -10 + max(9, 35) = 25 β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
FINAL: maxSum = 42
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Maximum path: 15 β 20 β 7
Sum: 15 + 20 + 7 = 42 β
Return Values vs Global Max:
Node returns (to parent) vs tracks (globally):
Node 15:
Returns: 15
Tracks: 15
Node 7:
Returns: 7
Tracks: 7
Node 20:
Returns: 20 + max(15, 7) = 35
Tracks: 20 + 15 + 7 = 42 β ANSWER!
Node 9:
Returns: 9
Tracks: 9
Node -10:
Returns: -10 + max(9, 35) = 25
Tracks: -10 + 9 + 35 = 34
Global max across all: 42 β
π Visual Path Analysis
Why Path 15β20β7 is Maximum:
Let's examine all major paths:
Path 1: Just node 20
20
Sum: 20
Path 2: 15 β 20
20
/
15
Sum: 35
Path 3: 20 β 7
20
\
7
Sum: 27
Path 4: 15 β 20 β 7 β
20
/ \
15 7
Sum: 42 β MAXIMUM!
Path 5: 9 β -10 β 20
-10
/ \
9 20
Sum: 19 (worse than path 4)
Path 6: 9 β -10 β 20 β 15
-10
/ \
9 20
/
15
Sum: 34 (worse than path 4)
Maximum: 42 from path 15β20β7
Why We Ignore Negative Contributions:
Consider if we DON'T max with 0:
Tree:
10
/
-5
Without max(0, left):
leftGain = -5
pathThrough = 10 + (-5) = 5
return = 10 + (-5) = 5
With max(0, left):
leftGain = max(0, -5) = 0
pathThrough = 10 + 0 = 10 β BETTER!
return = 10 + 0 = 10
Ignoring negative paths gives better results! π―
π Complexity Analysis
Time Complexity: O(n)
Visit each node exactly once
At each node: O(1) operations
- Two recursive calls (each visit child once)
- Math.max operations
- Addition
Total: n Γ O(1) = O(n)
Space Complexity: O(h)
Recursion call stack: O(h) where h = height
Best case (balanced): O(log n)
Worst case (skewed): O(n)
Global variable maxSum: O(1)
Total: O(h)
β οΈ Common Mistakes
Mistake 1: Returning Path Through Node
// β WRONG - Returns complete path
private int maxGain(TreeNode node) {
if (node == null) return 0;
int leftGain = maxGain(node.left);
int rightGain = maxGain(node.right);
leftGain = Math.max(0, leftGain);
rightGain = Math.max(0, rightGain);
// BUG: Returns path through node
return node.val + leftGain + rightGain; // β NOT EXTENDABLE!
}
// Problem: Parent can't extend this!
// Would create invalid paths
// β CORRECT - Return extendable path
return node.val + Math.max(leftGain, rightGain); β
Mistake 2: Not Tracking Global Max
// β WRONG - Only returns, doesn't track
private int maxGain(TreeNode node) {
if (node == null) return 0;
int leftGain = maxGain(node.left);
int rightGain = maxGain(node.right);
return node.val + Math.max(leftGain, rightGain);
}
// Problem: Misses path THROUGH nodes!
// Tree: 1
// / \
// 2 3
// Returns: 1 + max(2, 3) = 4
// But path 2β1β3 = 6 is better! β
// β CORRECT - Track global max
int pathThrough = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, pathThrough); β
Mistake 3: Not Handling Negative Paths
// β WRONG - Includes negative paths
private int maxGain(TreeNode node) {
if (node == null) return 0;
int leftGain = maxGain(node.left); // Might be negative!
int rightGain = maxGain(node.right); // Might be negative!
// Using negative values decreases sum!
int pathThrough = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, pathThrough);
return node.val + Math.max(leftGain, rightGain);
}
// Tree: 10
// /
// -20
// leftGain = -20
// pathThrough = 10 + (-20) = -10
// Should be just 10! β
// β CORRECT - Max with 0
leftGain = Math.max(0, leftGain); β
rightGain = Math.max(0, rightGain); β
Mistake 4: Wrong Initial maxSum
// β WRONG - Initial maxSum = 0
private int maxSum = 0; // β
// Problem: What if all nodes negative?
// Tree: -1
// /
// β WRONG - Initial maxSum = 0
private int maxSum = 0; // β
// Problem: What if all nodes negative?
// Tree: -1
// /
π― Pattern Recognition - Post-order with Global Tracking
Core Pattern: Return vs Track
Template for problems where:
- Need to consider ALL possible structures
- Different from what parent can use
Structure:
private GlobalValue global;
private ReturnValue dfs(node) {
if (node == null) return base;
left = dfs(node.left);
right = dfs(node.right);
// Calculate complete structure at node
complete = calculate(node, left, right);
// Update global with complete
global = update(global, complete);
// Return what parent can use
return extendable(node, left, right);
}
Related Problems:
1. Diameter of Binary Tree
Track: Path length through node
Return: Path length from node down
Same pattern!
2. Binary Tree Maximum Width
Track: Max width at any level
Return: Position info for parent
Global tracking!
3. Longest Univalue Path
Track: Path through node with same value
Return: Extendable univalue path
Same pattern!
4. Binary Tree Cameras
Track: Total cameras needed
Return: State (covered/has camera/needs camera)
Similar structure!
When to Use This Pattern:
β Answer is different from recursion return
β Need to consider complete structures
β Complete structure not extendable to parent
β Track global max/min/count
β Return value for parent's calculation
π Quick Revision Notes
π― Core Concept
Maximum path sum where path can go anywhere. Key insight: What node RETURNS (extendable path for parent = node + max(left, right)) vs what it TRACKS (complete path through node = node + left + right). Use global maxSum to track best complete path. Max with 0 to choose whether to extend path (NOT ignoring nodes!). Post-order: process children first, then combine at node.
β‘ Quick Implementation
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
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;
}
public static TreeNode buildTree(Integer[] arr) {
if (arr == null || arr.length == 0 || arr[0] == null) {
return null;
}
TreeNode root = new TreeNode(arr[0]);
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int i = 1;
while (!queue.isEmpty() && i < arr.length) {
TreeNode current = queue.poll();
// Process left child
if (i < arr.length) {
if (arr[i] != null) {
current.left = new TreeNode(arr[i]);
queue.offer(current.left);
}
i++;
}
// Process right child
if (i < arr.length) {
if (arr[i] != null) {
current.right = new TreeNode(arr[i]);
queue.offer(current.right);
}
i++;
}
}
return root;
}
public static void print(TreeNode root) {
if (root == null) {
System.out.println("[]");
return;
}
List<String> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node != null) {
result.add(String.valueOf(node.val));
queue.offer(node.left);
queue.offer(node.right);
} else {
result.add("null");
}
}
while (!result.isEmpty() && result.get(result.size() - 1).equals("null")) {
result.remove(result.size() - 1);
}
System.out.println(result);
}
}
class Solution {
public int maxPathSum(TreeNode root) {
// Approach - check visual explanation
// 2 important things for sum at a node: THROUGH and EXTENDINGUP
// THROUGH: A node can be parent of and the path is going through it
// EXTENDING_UP: A node can't be parent and it passes the sum upward to other node which would be parent.
// If its THROUGH: node_val + left + right
// If its EXTENDING_UP: node_val or node_val + max (left, right). You can only 1 path (left or right) with you in this case. Otherwise, it cannot be a VALID path when you extending up.
// Last important case: a node alone can contribute max sum sometimes of left or right would come with -ve value. In that case, you take it as 0.
int[] res = new int[] {Integer.MIN_VALUE};
maxPathSum(root, res);
return res[0];
}
private int maxPathSum(TreeNode root, int[] res) {
// base case
if(root == null) {
return 0;
}
int left = maxPathSum(root.left, res);
int right = maxPathSum(root.right, res);
// If left or right comes as 0, ignore it.
left = Math.max(left, 0);
right = Math.max(right, 0);
// THROUGH use case
res[0] = Math.max(res[0], root.val + left + right);
// EXTENDING_UP use case (this will not be result). So, just return to parent.
// Node itself or Node + left or Node + right
return Math.max(root.val, Math.max(root.val + left, root.val + right));
}
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.maxPathSum(TreeNode.buildTree(new Integer[] {1,2,3}))); // 6
System.out.println(s.maxPathSum(TreeNode.buildTree(new Integer[] {-10,9,20,null,null,15,7}))); // 42
}
}
π Key Insights
Return vs Track - The Core Distinction:
At each node, TWO calculations:
1. Path THROUGH node (complete, not extendable):
node.val + leftGain + rightGain
β Track in global maxSum
β This might be the answer!
β EVERY node considered (even if gain = 0)
2. Path FROM node (extendable to parent):
node.val + max(leftGain, rightGain)
β Return to parent
β Parent can extend this!
Why different?
If return complete path:
Parent
|
Node
/ \
Left Right
Parent would try: Parent + (Node+Left+Right)
But this creates: LeftβNodeβRightβParent
Node used as junction with BOTH children β INVALID! β
Must return ONE branch only!
This is the HARDEST part to understand! π
Max with 0 - CRITICAL CLARIFICATION:
leftGain = Math.max(0, leftGain)
rightGain = Math.max(0, rightGain)
This is NOT "ignoring negative nodes"!
This is "choosing not to EXTEND path if it hurts"!
What it means:
- If childGain > 0: Include child in path (helps!)
- If childGain <= 0: Don't extend to child (hurts!)
But EVERY node is still considered individually!
Because we ALWAYS execute:
maxSum = Math.max(maxSum, node.val + leftGain + rightGain)
Example 1: Mixed positive/negative
10
/
-20
leftGain = max(0, -20) = 0
pathThrough = 10 + 0 = 10
Answer: 10 (just node 10, don't extend to -20)
Example 2: ALL negative nodes
-1
/ \
-2 -3
Node -2: pathThrough = -2, tracked! β
Node -3: pathThrough = -3, tracked! β
Node -1:
leftGain = max(0, -2) = 0 (don't extend)
rightGain = max(0, -3) = 0 (don't extend)
pathThrough = -1 + 0 + 0 = -1, tracked! β
maxSum evolution: -β β -2 β -2 β -1
Answer: -1 (single node, best option!)
All nodes were considered!
We just chose not to combine them because
combining makes it worse:
-1 alone (-1) better than -1+-2 (-3) or -1+-3 (-4)
"Don't extend path to child if it decreases sum!" π―
Why This Design Works for All Cases:
GUARANTEE: Every node considered as single-node path
Because this line ALWAYS executes:
maxSum = Math.max(maxSum, node.val + leftGain + rightGain)
When both children hurt (negative):
leftGain = 0, rightGain = 0
Then:
maxSum = Math.max(maxSum, node.val + 0 + 0)
maxSum = Math.max(maxSum, node.val)
So single node IS considered! β
The algorithm evaluates:
β Single nodes: [node]
β Node + left: [left, node] if left helps
β Node + right: [node, right] if right helps
β Complete: [left, node, right] if both help
"Help" means positive contribution (gain > 0)
Global Variable Necessity:
Why global maxSum?
Can't return it because:
- Parent needs extendable value
- Complete path not extendable
Two separate concerns:
1. What helps parent (return)
2. What might be answer (global)
Global variable elegantly solves this! β¨
Initialize with Integer.MIN_VALUE:
- Handles all-negative trees
- Any real path sum will be larger
- First node updates it immediately
πͺ Memory Aid
"Track complete, return extendable!"
"Max(0, gain) = choose not to extend if hurts!"
"ALL nodes considered, just don't combine if worse!"
"Global max, local return!"
"Post-order: children first!" β¨
β οΈ Don't Forget
- Track path THROUGH (node + left + right) - ALWAYS!
- Return path FROM (node + max(left, right))
- Max with 0 (choose not to extend, NOT ignore nodes!)
- Global maxSum (track answer separately)
- Initialize MIN_VALUE (handles all-negative trees!)
- Every node evaluated (as single-node path minimum)
- Post-order (process children first!)
π Congratulations!
You've mastered one of the HARDEST tree problems!
What You Learned:
β
Return vs Track - Core distinction
β
Global variable - Tracking separately
β
Post-order processing - Children first
β
Negative handling - Choice not to extend
β
Extendable paths - What parent can use
β
Complete paths - What might be answer
β
All-negative trees - Every node still considered
Why This Problem is Special:
This problem teaches:
1. Return value β Answer
2. Global state for tracking
3. Post-order aggregation
4. Strategic path extension
5. Path extendability concept
6. Handling all edge cases
All fundamental concepts! π
Interview Mastery:
Interviewer: "Maximum path sum"
Your explanation:
"Need to distinguish two things:
1. Path THROUGH node: complete path
β Track in global max
β Might be answer
β Every node considered
2. Path FROM node: extendable
β Return to parent
β Parent can extend
Use post-order DFS with global tracking.
Max with 0 to choose whether to extend path.
Handles all-negative trees correctly.
Time: O(n), Space: O(h)"
Shows deep understanding! π―
You've conquered a HARD problem with complete understanding! πβ¨π―