Maximum Sum Path In A Binary Tree

14 min read

The maximum sum path in a binary tree is the path between any two nodes that produces the largest possible sum of node values. Also, because paths may begin and end at different locations, this problem is more challenging than finding a maximum root-to-leaf sum. A depth-first search can solve it efficiently by evaluating each node as the highest point of a potential path.

Understanding the Maximum Sum Path Problem

A binary tree consists of nodes, where each node has at most two children: a left child and a right child. A path moves through connected nodes without revisiting an ancestor or branching into two directions at the same time Small thing, real impact..

To give you an idea, consider this tree:

        10
       /  \
     -2    7
    / \   / \
   8  -4 -5  1

One possible path is 8 → -2 → 10 → 7, with a sum of 23. Another is simply the single node 10, with a sum of 10. In this tree, the maximum sum path is:

8 → -2 → 10 → 7

Its total is:

8 + (-2) + 10 + 7 = 23

The path is valid because every pair of adjacent nodes is connected, and it does not split into both the left and right subtrees No workaround needed..

Important Definition: Any-Node Path Versus Root-to-Leaf Path

The maximum sum path problem has several related versions. Before choosing an algorithm, it is important to identify which definition applies.

Any-node to any-node path

This is the most common interpretation of the maximum sum path problem. The path can start and end at any nodes. It may pass through an internal node and combine one downward path from the left subtree with one downward path from the right subtree.

And yeah — that's actually more nuanced than it sounds.

Root-to-leaf path

A root-to-leaf path must begin at the root and end at a leaf. And it cannot turn upward and then descend through the other child of an ancestor. That's why, the general any-node path algorithm is not automatically suitable for this version That's the part that actually makes a difference. Nothing fancy..

Lowest-common-ancestor path

Sometimes the task is to find the maximum-sum path specifically between two given nodes. In that case, the path normally passes through their lowest common ancestor. The solution requires calculating maximum downward sums from the target nodes and combining them at their meeting point Nothing fancy..

The following explanation focuses on the standard maximum sum path in a binary tree, where the path may begin and end at any nodes.

Why a Simple Traversal Is Not Enough

A basic tree traversal visits every node, but it does not necessarily discover the best complete path. A maximum path can pass through an ancestor after coming from one child and then continue into the other child.

For example:

       5
      / \
     9  -10
    /
   20

The path 20 → 9 → 5 → -10 has a sum of 24. If a traversal considers only root-to-leaf paths, it would miss this valid path because -10 is not a leaf when viewed from the path’s perspective.

A useful maximum path may also contain negative values. Excluding a negative child can improve the answer, but excluding it entirely may prevent a valuable positive branch from being connected through the current node. This tension between a complete path and a reusable branch is the central idea behind the efficient algorithm Took long enough..

Core Insight Behind the Algorithm

At every node, there are two different quantities to consider:

  1. The best path that stops at the current node.
  2. The best downward path that can be passed to the current node’s parent.

A path that stops at the current node can combine:

  • The current node’s value.
  • The best downward path from its left subtree.
  • The best downward path from its right subtree.

Even so, a parent can use only one child branch. That's why, the value returned to the parent must choose either the left branch or the right branch, not both Not complicated — just consistent. Surprisingly effective..

For a node named node, define:

left_branch  = maximum sum from node.left down to some descendant
right_branch = maximum sum from node.right down to some descendant

The best complete path using node as its highest node is:

node.value + max(0, left_branch) + max(0, right_branch)

The best branch that can be extended upward is:

node.value + max(0, max(left_branch, right_branch))

Don't overlook the max(0, value) operation. It carries more weight than people think. It prevents a negative branch from reducing the sum of a path that could otherwise begin at the current node.

Step-by-Step Algorithm

The problem can be solved with a recursive depth-first search in postorder. Each child is processed before its parent.

  1. Recursively calculate the best downward sum from the left child.
  2. Recursively calculate the best downward sum from the right child.
  3. Ignore negative child contributions when constructing a complete path.
  4. Calculate the best path that has the current node as its highest point.
  5. Update the global maximum if this path is better.
  6. Return the best single branch that the current node can offer to its parent.

A concise version of the logic is:

function dfs(node):
    if node is null:
        return 0

    left = max(0, dfs(node.left))
    right = max(0, dfs(node.right))

    complete_path = node.value + left + right
    update global maximum with complete_path

    return node.value + max(left, right)

Although null nodes return zero, the final answer should not default to zero for a tree containing only negative values. The global maximum must be initialized from the first valid node or updated carefully during traversal Most people skip this — try not to..

Python Implementation

class TreeNode:
    def __init__(self, value=0

```python
class TreeNode:
    def __init__(self, value=0, left=None, right=None):
        self.value = value
        self.left = left
        self.right = right

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        self.global_max = float('-inf')

        def dfs(node):
            if node is None:
                return 0

            left = max(0, dfs(node.left))
            right = max(0, dfs(node.right))

            complete_path = node.Here's the thing — value + left + right
            self. global_max = max(self.

            return node.value + max(left, right)

        dfs(root)
        return self.global_max

The TreeNode class defines the standard structure for each element in the binary tree, holding a value and references to its left and right children. The Solution class encapsulates the main method maxPathSum, which accepts the root of the tree and returns the maximum path sum. Inside this method, global_max is initialized to negative infinity so that even if every node in the tree carries a negative value, the algorithm will still correctly identify the single largest (least negative) node as the answer.

The nested dfs function performs the recursive postorder traversal described earlier. For each node, it computes the best contributions from both subtrees, clamps them to zero if they are negative, calculates the complete path passing through the current node, updates the global maximum if needed, and finally returns the best single branch that can be extended upward. Once the entire tree has been traversed, global_max holds the final result.

Most guides skip this. Don't.

Time and Space Complexity

The algorithm visits every node in the tree exactly once, performing a constant amount of work at each node. In the worst case of a completely unbalanced tree, h can equal n, making the space complexity O(n). Also, the space complexity is O(h), where h is the height of the tree, due to the recursive call stack. This gives a time complexity of O(n), where n is the total number of nodes. For a balanced tree, h is O(log n), which is significantly more efficient.

Worth pausing on this one.

Walkthrough Example

Consider the following binary tree:

       -10
       /  \
      9    20
          /  \
         15   7

The algorithm processes the nodes in this order:

  • Node 9: Both children are null, so left = 0 and right = 0. The complete path is 9 + 0 + 0 = 9. The returned branch value is 9.
  • Node 15: Both children are null. The complete path is 15. The returned branch value is 15.
  • Node 7: Both children are null. The complete path is 7. The returned branch value is 7.
  • Node 20: left = max(0, 15) = 15 and right = max(0, 7) = 7. The complete path is 20 + 15 + 7 = 42. The returned branch value is 20 + 15 = 35.
  • Node -10: left = max(0, 9) = 9 and right = max(0, 35) = 35. The complete path is -10 + 9 + 35 = 34. The global maximum is updated to 42 from node 20's complete path.

The final answer is 42, corresponding to the path 15 → 20 → 7.

Common Pitfalls and Edge Cases

Several edge cases deserve attention. First, when all node values are negative, the algorithm must still return the maximum single node value rather than zero. This is handled by initializing global_max to negative infinity and using max(0, ...Practically speaking, ) only for branch contributions, not for updating the global maximum itself. Second, a tree with a single node should return that node's value, which the algorithm handles naturally since both child branches return zero and the complete path equals the node's value. And third, extremely deep trees may cause a stack overflow in languages with limited recursion depth, such as Python, which defaults to a recursion limit around 1000. In such cases, an iterative postorder traversal using an explicit stack can be substituted That's the whole idea..

Broader Applications

The pattern demonstrated here—distinguishing between a local result and a value that can be propagated upward—is a powerful recurring theme in tree algorithms. It appears in problems involving diameter calculation, serialization and deserialization,

path validation, and subtree aggregation. In each case, a node must answer two related questions:

  1. What is the best result found entirely inside this subtree?
  2. What is the best result that can be extended upward to the parent?

Here's one way to look at it: in the binary tree diameter problem, the local result at a node may combine the heights of both left and right subtrees, while the value returned upward can only include one of those subtrees. This is nearly the same structural idea used in the maximum path sum algorithm: the best complete result and the best extendable result are not always the same.

This is where a lot of people lose the thread.

Related Problem Variations

Once this pattern is understood, several variations become much easier to solve.

  • Maximum root-to-leaf path sum:
    Instead of allowing the path to start and end anywhere, the path must begin at the root and end at a leaf. In this case, there is no need to combine both child branches at a node. The recursive function simply returns the maximum sum from the current node down to a leaf And that's really what it comes down to..

  • Minimum path sum:
    This is similar to the maximum path sum problem, but with min operations instead of max. Care must be taken with initialization, especially when negative values are involved That's the part that actually makes a difference..

  • Path sum with a target value:
    If the goal is to determine whether any downward path equals a target sum, prefix sums or recursive subtraction can be used depending on whether the path must start at the root or may start at any node Simple, but easy to overlook..

  • Counting maximum paths:
    Some versions ask not only for the maximum path sum, but also for the number of paths that achieve it. This requires tracking both the best value and the count of ways to obtain that value.

  • Constrained path length:
    If the path may contain at most k nodes, the recursion must carry additional state. This turns the problem into a dynamic programming problem on trees.

  • N-ary tree maximum path sum:
    For trees where each node can have more than two children, the same principle applies. The best complete path through a node may combine the two largest positive child contributions, while the branch returned upward uses only the largest one It's one of those things that adds up. Simple as that..

Practical Implementation Tips

When implementing this algorithm, a few details can prevent subtle bugs.

First, use an appropriately large numeric type if the tree may contain large values. In languages such as C++ or Java, integer overflow can produce incorrect results even if the traversal logic is correct Simple, but easy to overlook..

Second, be careful with global state. A global variable such as global_max is convenient, but it can make the function harder to reuse in larger systems. An alternative

Instead of a global variable, one common approach is to pass a reference (or pointer) to the answer as a parameter, allowing the recursive function to update it directly. In languages like C++ or Java this can be done with a non‑const reference or an array of length one, while Python programmers often wrap the result in a mutable container such as a list. By keeping the answer outside the recursion’s return value, each call remains focused on computing the best contribution of its subtree, and the caller can still propagate the best complete path found anywhere in the tree Less friction, more output..

int maxPathSum(TreeNode* root, int& maxSum) {
    maxSum = std::numeric_limits::min();
    dfs(root, maxSum);
    return maxSum;
}
int dfs(TreeNode* node, int& maxSum) {
    if (!node) return 0;
    int left  = std::max(dfs(node->left, maxSum), 0);
    int right = std::max(dfs(node->right, maxSum), 0);
    int cur   = node->val + left + right;
    maxSum    = std::max(maxSum, cur);
    return node->val + std::max(left, right);
}

The same pattern works in Java with an int[] holder, or in Python with a one‑element list. It eliminates the need for a globally visible constant and makes the function easier to unit‑test, because the container can be inspected after the recursion finishes Turns out it matters..

Beyond the global‑state concern, a few additional details often trip up developers:

  • Initialization of the answer. The initial value for maxSum (or its equivalent) should be the smallest possible number for the type in use. Using INT_MIN in C++ or Integer.MIN_VALUE in Java guarantees that even a tree consisting solely of a single very negative node will be correctly captured.
  • Handling empty trees. If the input tree can be null, the wrapper should either return a sentinel value (e.g., INT_MIN) or throw a clear exception, depending on the problem’s contract.
  • Overflow safety. When the tree may contain large values, consider using a 64‑bit integer (long in Java, long long in C++) even if the problem statement promises 32‑bit results, because intermediate sums can exceed the range before the final answer is taken.
  • Negative contributions. The classic solution discards negative child contributions by taking max(child, 0). This works when the goal is to maximize the sum, but if the problem asks for the minimum path sum, the analogous step is min(child, 0) and the initialization must be the largest possible value instead.

By adhering to these practices—explicit state passing, careful initialization, overflow awareness, and proper handling of edge cases—the algorithm becomes reliable and reusable across the many variations described earlier. Whether you are solving the classic maximum path sum, counting how many paths achieve the optimum, limiting path length, or extending to N‑ary trees, the core insight remains the same: at each node you must distinguish between the best extendable contribution (which can be carried upward) and the best complete path (which may combine both children). Mastering this distinction, together with clean implementation techniques, equips you to tackle any tree‑path problem with confidence Easy to understand, harder to ignore..

Simply put, the key takeaways are:

  1. Separate concerns – compute the best extendable branch and the best full path separately at each node.
  2. Avoid global state – use a reference or mutable container to communicate the global maximum without polluting the namespace.
  3. Initialize safely – start the answer with the extreme value appropriate for the operation (max or min).
  4. Guard against overflow – promote to a larger type when intermediate sums could exceed the guaranteed range.
  5. Handle edge cases – empty trees, all‑negative values, and N‑ary structures all follow the same pattern with minor adjustments.

With these principles in place, you can extend the solution to any of the listed variations or even invent new ones, knowing that the underlying algorithm remains both elegant and reliable

across platforms and problem constraints Small thing, real impact..

A final practical step is to test the recursive helper with cases that expose the two different notions of path. To give you an idea, a root with two positive children checks whether the implementation combines both sides for the complete path while returning only one side upward. A chain of negative nodes checks initialization, and a tree with a large positive subtree attached to a negative parent checks whether negative contributions are discarded correctly.

Out This Week

Hot Off the Blog

Same Kind of Thing

Follow the Thread

Thank you for reading about Maximum Sum Path In A Binary Tree. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home