DP on Trees Maximum Path Sum in DSA Go - Time & Space Complexity
We want to understand how the time needed grows when finding the maximum path sum in a tree using dynamic programming.
How does the number of steps change as the tree gets bigger?
Analyze the time complexity of the following code snippet.
// Tree node structure
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func maxPathSum(root *TreeNode) int {
maxSum := root.Val
var dfs func(node *TreeNode) int
dfs = func(node *TreeNode) int {
if node == nil {
return 0
}
left := max(0, dfs(node.Left))
right := max(0, dfs(node.Right))
maxSum = max(maxSum, node.Val + left + right)
return node.Val + max(left, right)
}
dfs(root)
return maxSum
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
This code finds the maximum sum of values along any path in a binary tree by visiting each node once.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive depth-first search (dfs) visiting each node once.
- How many times: Each node is visited exactly one time during the recursion.
As the tree grows, the number of nodes increases, and the function visits each node once.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 visits |
| 100 | About 100 visits |
| 1000 | About 1000 visits |
Pattern observation: The number of operations grows roughly in direct proportion to the number of nodes.
Time Complexity: O(n)
This means the time needed grows linearly with the number of nodes in the tree.
[X] Wrong: "Because the function calls itself twice, the time is O(2^n)."
[OK] Correct: Each node is visited only once, not repeatedly, so the total work is proportional to the number of nodes, not exponential.
Understanding this linear time complexity helps you explain how efficient tree traversals with dynamic programming work, a common skill interviewers look for.
"What if we changed the tree to a graph with cycles? How would the time complexity change?"
Practice
Solution
Step 1: Understand the problem definition
The maximum path sum is about finding the path between any two nodes that gives the highest sum of node values.Step 2: Differentiate from other sums
It is not just the sum of all nodes or the longest path length, but the maximum sum along any path.Final Answer:
The largest sum of node values on any path between two nodes in the tree -> Option AQuick Check:
Maximum path sum = largest sum on any path [OK]
- Confusing maximum path sum with longest path length
- Thinking it is sum of all nodes
- Considering only single node values
Solution
Step 1: Identify correct parameter type
The function should accept a pointer to TreeNode to handle nil nodes and avoid copying.Step 2: Identify correct return type
The function returns an integer representing the maximum path sum, so return type int is correct.Final Answer:
func maxPathSum(node *TreeNode) int -> Option DQuick Check:
Pointer input and int output for max sum [OK]
- Passing node by value instead of pointer
- Returning string or error instead of int
- Using wrong parameter type like int
package main
import "fmt"
type TreeNode struct {
Val int
Left, Right *TreeNode
}
func maxPathSumUtil(node *TreeNode, maxSum *int) int {
if node == nil {
return 0
}
left := max(0, maxPathSumUtil(node.Left, maxSum))
right := max(0, maxPathSumUtil(node.Right, maxSum))
*maxSum = max(*maxSum, left + right + node.Val)
return max(left, right) + node.Val
}
func maxPathSum(root *TreeNode) int {
maxSum := -1000000
maxPathSumUtil(root, &maxSum)
return maxSum
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
root := &TreeNode{Val: -10}
root.Left = &TreeNode{Val: 9}
root.Right = &TreeNode{Val: 20}
root.Right.Left = &TreeNode{Val: 15}
root.Right.Right = &TreeNode{Val: 7}
fmt.Println(maxPathSum(root))
}Solution
Step 1: Calculate max path sums for subtrees
Left subtree: 9 (updates maxSum to 9). Right subtree: 15 (maxSum=15), 7 (no change), at 20: left=15, right=7, *maxSum = max(15, 15+7+20)=42, returns max(15,7)+20=35.Step 2: Compare and update maxSum
At root (-10): left=max(0,9)=9, right=max(0,35)=35, *maxSum = max(42, 9+35+(-10))=max(42,34)=42.Final Answer:
42 -> Option CQuick Check:
Max path sum = 42 [OK]
- Not ignoring negative sums by using max(0, ...)
- Adding node values incorrectly
- Confusing max path sum with max root-to-leaf sum
func maxPathSumUtil(node *TreeNode, maxSum *int) int {
if node == nil {
return 0
}
left := maxPathSumUtil(node.Left, maxSum)
right := maxPathSumUtil(node.Right, maxSum)
*maxSum = max(*maxSum, left + right + node.Val)
return max(left, right) + node.Val
}Solution
Step 1: Check handling of negative child sums
The function uses left and right sums directly without ignoring negative values, which can reduce the path sum.Step 2: Identify fix by ignoring negatives
To fix, use max(0, left) and max(0, right) to ignore negative sums before adding to node.Val.Final Answer:
It does not ignore negative sums from children before adding -> Option AQuick Check:
Ignore negative child sums for correct max path [OK]
- Not using max(0, child) to ignore negatives
- Returning sum of both children instead of max child
- Not updating maxSum properly
Solution
Step 1: Handle negative child sums carefully
Use max(0, child sums) to ignore negative paths that reduce the total sum.Step 2: Update global maximum path sum correctly
Update the global max with the sum of left + right + current node value to consider paths passing through the node.Final Answer:
Use max(0, child sums) to ignore negative paths and update global max with left + right + node.Val -> Option BQuick Check:
Ignore negatives, update max with left+right+node [OK]
- Summing all nodes including negatives blindly
- Considering only root-to-leaf paths
- Removing negative nodes instead of ignoring sums
Ready to prove it?
Pass the test and this pattern moves unknown
