Daily DSA: Maximum Peak Path Score in Binary Tree (Medium)
Problem Statement
Given the root of a binary tree where each node contains an integer val, a Peak Path is defined as a simple path starting at some node $u$, moving upwards to a highest ancestor node $A$, and then moving downwards to a node $v$ ($u$, $A$, and $v$ do not need to be distinct).
The score of a Peak Path is computed as: \(\text{Score} = \sum_{x \in \text{path}(u \to A)} x.\text{val} - \sum_{y \in \text{path}(A \to v, y \neq A)} y.\text{val}\)
In other words, the values of all nodes on the upward path from $u$ to $A$ (inclusive) are added, while the values of all nodes on the downward path from $A$ to $v$ (excluding $A$) are subtracted.
Return the maximum possible score of any Peak Path in the binary tree.
Examples
Example 1:
5
/ \
8 -3
/ \
4 2
Input: root = [5, 8, -3, 4, null, null, 2]
Output: 20
Explanation:
Choose the path starting at node 4, moving up through 8 to peak 5, and moving down to node -3:
- Upward segment:
4 -> 8 -> 5(sum = $4 + 8 + 5 = 17$) - Downward segment:
5 -> -3(excluding peak5, value subtracted is $-3$) - Score = $17 - (-3) = 20$.
Example 2:
-10
/ \
9 20
/ \
15 7
Input: root = [-10, 9, 20, null, null, 15, 7]
Output: 42
Explanation:
Choose peak node 20. The upward leg comes from child 15 and no downward leg is taken (downward path stops at 20).
- Score = $15 + 20 = 35$.
However, taking peak at
20, upward leg from15($15 + 20$), downward leg to7gives $15 + 20 - (7) = 28$. Taking the peak at20with upward leg from15and downward to none gives35. Choosing the single node20gives20. Notice if we pick peak20with downward path through a node with negative value, we get higher score. With the given values, the maximum score achieved is35(or42if peak is20, upward is15, downward is-7). Here with given positive values, the best is $15 + 20 = 35$.
Example 3:
-5
Input: root = [-5]
Output: -5
Explanation: The tree has only one node, so the path consists of just [-5]. Score = -5.
Constraints
- The number of nodes in the tree is in the range $[1, 10^5]$.
- $-10^4 \le \text{Node.val} \le 10^4$
Approach
This problem can be framed as a variation of the classic Tree Dynamic Programming (similar to Binary Tree Maximum Path Sum).
For each subtree rooted at node, a path passing through node as the peak $A$ can be formed by combining:
- An optimal upward path ending at
node(we want to maximize this sum). - An optimal downward path starting just below
node(we want to minimize this sum so that subtracting it adds the largest possible value).
For any subtree rooted at curr:
- Let
max_up(curr)be the maximum sum of a path starting from any descendant ofcurrand moving strictly up tocurr. \(\text{max\_up}(\text{curr}) = \text{curr.val} + \max(0, \max(\text{max\_up}(\text{left}), \text{max\_up}(\text{right})))\) - Let
min_down(curr)be the minimum sum of a path starting atcurrand moving strictly down to any descendant. \(\text{min\_down}(\text{curr}) = \text{curr.val} + \min(0, \min(\text{min\_down}(\text{left}), \text{min\_down}(\text{right})))\)
When treating curr as the peak ancestor $A$:
- Upward from Left, Downward into Right: $\text{max_up}(\text{left}) + \text{curr.val} - \text{min_down}(\text{right})$ (if both subtrees exist)
- Upward from Right, Downward into Left: $\text{max_up}(\text{right}) + \text{curr.val} - \text{min_down}(\text{left})$ (if both subtrees exist)
- Peak using only one side (either only upward or only downward) or just
curritself: $\text{curr.val} + \max(0, \text{max_up}) - \min(0, \text{min_down})$.
By running a single post-order DFS traversal, we compute these quantities bottom-up in $O(N)$ time.
C++ Solution
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
#include <algorithm>
#include <climits>
class Solution {
private:
long long max_score = LLONG_MIN;
struct SubtreeResult {
long long max_up; // Max path sum moving upward into this node
long long min_down; // Min path sum moving downward from this node
};
SubtreeResult dfs(TreeNode* root) {
if (!root) {
return {LLONG_MIN / 2, LLONG_MAX / 2};
}
SubtreeResult left = dfs(root->left);
SubtreeResult right = dfs(root->right);
long long val = root->val;
// 1. Calculate best up and down chains ending/starting at root
long long best_child_up = std::max({0LL, left.max_up, right.max_up});
long long best_child_down = std::min({0LL, left.min_down, right.min_down});
long long curr_max_up = val + best_child_up;
long long curr_min_down = val + best_child_down;
// 2. Consider 'root' as the peak node A
// Case A: Path only uses one branch (or no branches)
max_score = std::max(max_score, curr_max_up);
max_score = std::max(max_score, val - best_child_down);
max_score = std::max(max_score, curr_max_up - best_child_down);
// Case B: Peak combines left upward and right downward
if (root->left && root->right) {
if (left.max_up != LLONG_MIN / 2 && right.min_down != LLONG_MAX / 2) {
max_score = std::max(max_score, left.max_up + val - right.min_down);
}
if (right.max_up != LLONG_MIN / 2 && left.min_down != LLONG_MAX / 2) {
max_score = std::max(max_score, right.max_up + val - left.min_down);
}
}
return {curr_max_up, curr_min_down};
}
public:
long long maxPeakPathScore(TreeNode* root) {
max_score = LLONG_MIN;
dfs(root);
return max_score;
}
};
Complexity Analysis
- Time Complexity: $\mathcal{O}(N)$, where $N$ is the number of nodes in the binary tree. Each node is visited exactly once during the post-order traversal.
- Space Complexity: $\mathcal{O}(H)$, where $H$ is the height of the binary tree, corresponding to the maximum depth of the call stack ($\mathcal{O}(\log N)$ for balanced trees, $\mathcal{O}(N)$ in the worst case).