Algorithm

Binary Tree Maximum Path Sum

Trees Pattern

Binary Tree Maximum Path Sum

A path is any sequence of nodes where each consecutive pair is joined by an edge, and no node appears twice. It does not have to pass through the root, and it does not have to start or end at a leaf. Given the root of a binary tree, return the largest possible sum of the values along such a path. The path must contain at least one node, so when every value is negative the answer is the single largest (least negative) value, never 0.

CONSTRAINTS
  • The number of nodes in the tree is in the range [1, 3 × 10⁴]
  • -1000 <= Node.val <= 1000
  • The path must be non-empty — the empty path is not a valid answer
  • The path need not include the root or any leaf
EXAMPLE 1
Input: root = [1,2,3]
Output: 6
The path 2 → 1 → 3 collects all three values. It bends at the root, which is allowed because no node is repeated.
EXAMPLE 2
Input: root = [-10,9,20,null,null,15,7]
Output: 42
15 + 20 + 7 = 42, a path that bends at 20 and never touches the root. Routing through the root would force -10 into the total and also give up one of 15 or 7.
EXAMPLE 3
Input: root = [-3]
Output: -3
A path must contain at least one node, so the least-bad option is the single node itself. Returning 0 by 'taking nothing' is not permitted.
EXAMPLE 4
Input: root = [2,-1,-2]
Output: 2
Both children would lower the total, so the best path is the root on its own. A path is free to stop anywhere — it is never obliged to descend.
Must the path touch the root, or end at leaves?
Neither. It can be any connected, non-repeating route, including a single node in the middle of the tree.
What if every value is negative?
You still return the largest single value, because the path cannot be empty. Any solution that starts a maximum at 0 gets this case wrong.
Can a path branch at more than one node?
No. It changes direction at most once — going up and then down again would revisit the node you turned at. That single-bend restriction is what makes the problem solvable in one pass.
Is the answer the path itself or just the sum?
Just the sum. Reconstructing the actual path is a reasonable follow-up and needs you to remember where each best value came from.

This is Diameter of Binary Tree with values instead of lengths, and it is worth holding that comparison in mind, because the skeleton is identical and every difference comes from one fact: lengths are always positive, and values can be negative.

Start with the same structural observation. A path never repeats a node, and edges only run downward, so any path climbs to a single highest node and then descends — one bend, never two. Group the paths by that bend, ask each node "what is the best path that bends at you?", and take the maximum over all nodes. Every path is counted at exactly one node, so nothing is missed.

What a node offers upward is not what a node can achieve

Sit on node v and hold two different quantities apart. They get confused constantly, and keeping them separate is the whole problem.

The best path that bends at v. It may descend into the left subtree and into the right subtree, so it is v.val plus the best downward run on the left plus the best downward run on the right. This is a candidate for the final answer, but it is finished — it cannot be extended any further, because v has already spent both of its downward links.

The best path that passes through v on its way up. If v's parent wants to build a path through v, that path arrives from above and can continue into only one of v's subtrees; taking both would create a branch point and stop being a path. So what v can offer its parent is v.val plus the better of its two downward runs — never the sum of both.

Only one of these can be the return value, and it has to be the second, since the recursion cannot proceed without it. The first is therefore recorded into a variable outside the recursion, exactly as the diameter was.

Negative subtrees are optional, and that changes everything

In the diameter problem, a deeper subtree was always at least as good, so we took whatever the children offered. Here a child may offer a negative number — a subtree stuffed with losses — and a path is never obliged to descend into it. Declining costs nothing.

So clamp each child's offer at zero: gain = max(0, child's offer). Read that as "use this side only if it helps." A clamped 0 means the path simply stops at v on that side.

python
best = float('-inf')

def gain(node):
    nonlocal best
    if node is None:
        return 0                                   # an absent side contributes nothing
    left  = max(0, gain(node.left))                # take the side only if it is a gain
    right = max(0, gain(node.right))
    best = max(best, node.val + left + right)      # candidate: the path BENDS here
    return node.val + max(left, right)             # offer upward: the path PASSES through

gain(root)
return best
Crucial Notenode.val itself is never clamped — only the children's offers are. This is what makes the all-negative case come out right. On a tree holding just -3, both sides clamp to 0 and the candidate is -3 + 0 + 0 = -3, so best becomes -3. Had we clamped the node's own value too, or initialised best to 0, the answer would be 0 — a path containing no nodes, which the problem forbids. Initialise the running maximum to negative infinity, not zero, for the same reason: zero is a claim that the empty path is legal.

Also note the deliberate asymmetry between the two lines: the recorded candidate adds both sides, the returned offer takes the better one. That is the same + versus max split as in Diameter, and for the same geometric reason — a path that ends here may use both directions; a path that continues upward may use only one.

Worked example:root -10, with children 9 and 20; 20 has children 15 and 7
- Leaf 15: both sides clamp to 0. Candidate 15 + 0 + 0 = 15, so best = 15. Offers upward 15 + max(0,0) = 15.
- Leaf 7: candidate 7, best stays 15. Offers 7.
- Node 20: left gain = max(0, 15) = 15, right gain = max(0, 7) = 7. Candidate 20 + 15 + 7 = 42, so best = 42 — this is the path 15 → 20 → 7. Offers upward 20 + max(15, 7) = 35, dropping the 7 because a path continuing to the parent can only use one branch.
- Leaf 9: candidate 9, best stays 42. Offers 9.
- Root -10: left gain = max(0, 9) = 9, right gain = max(0, 35) = 35. Candidate -10 + 9 + 35 = 34, which loses to 42. Offers upward -10 + 35 = 25, which nobody reads.
- Answer: 42.

The root is the instructive step. It genuinely improved on nothing — its own value is a loss — and the winning path was recorded three calls earlier, deep in the right subtree. Recording a candidate at every node is what lets the answer live anywhere in the tree without any special handling.

Now run the fourth example, root 2 with children -1 and -2. Both children offer negative numbers, so both clamp to 0, and the candidate at the root is 2 + 0 + 0 = 2. The clamping is doing real work here: without it the answer would be 2 + (-1) + (-2) = -1, a path forced to swallow losses it was free to skip.

Cost, and the pattern completed

One visit per node, constant work each: O(N) time, O(H) space for the recursion stack. The brute-force alternative — compute, for every node, the best downward run on each side by re-walking the subtrees — costs O(N × H), and it is the same duplicated-walk waste that Balanced Binary Tree and Diameter both had. The fix is the same fusion.

What generalises here is a way of thinking rather than a formula. When a recursion's local answer and its reportable value differ, write them down as two separate sentences before writing any code: what does my parent need from me? and what is the best answer that peaks right here? Then decide which one is the return value (the one the recursion cannot continue without) and put the other in an accumulator. Add one more question whenever values may be negative — am I allowed to decline this piece? — and the clamp appears exactly where it belongs. Those three questions are enough to reconstruct this solution from scratch, which is far more useful than remembering it.

Interactive Strategy Visualization

Optimal Path Explorer

Max Gain and Peak Analysis
10920157
Current Peak Sum0
Global Maximum0
Algorithm Strategy

Starting Post-order traversal. Nodes return 'Gain' (Node + Max Child).

Global High Score
0
Complexity Note
Gain = Node + Math.max(0, LeftGain, RightGain).

Strategy

Focus on the recursive nature of trees: solve for subtrees and combine results at the root.

"Divide and Conquer: Subproblem → Recurrence → Result"

O(N × H) Recompute Downward Runs Per Node
O(N) Time · O(H) Space Fused Pass With Clamped Gains