Description
給定一棵二元樹,找到路徑和最大的路徑。路徑定義為從任意節點出發沿著父子連接到達任意節點的序列,路徑不需要經過根節點,每個節點最多出現一次。
Example:
Input: root = [-10,9,20,null,null,15,7] Output: 42 (路徑為 15 -> 20 -> 7)
Intuition#
對每個節點,計算「經過它的最大路徑和」= 節點值 + 左邊最大貢獻 + 右邊最大貢獻;但回傳給父節點時只能選一邊。
- 關鍵區分:
- 「經過該節點的完整路徑」可以同時包含左右子路徑(用於更新全域最大值)
- 「回傳給父節點的貢獻」只能選左或右其中一邊(因為路徑不能分叉)
- 貢獻值為負時,取 0(不選該子路徑)
Approaches#
1: Brute Force(枚舉所有路徑)#
- 概念: 對每個節點作為路徑的最高點,嘗試所有可能的左右延伸組合
- 時間複雜度:
O(n^2) - 空間複雜度:
O(n)
class Solution {
fun maxPathSum(root: TreeNode?): Int {
var maxSum = Int.MIN_VALUE
fun maxGain(node: TreeNode?): Int {
if (node == null) return 0
return node.`val` + maxOf(maxGain(node.left), maxGain(node.right), 0)
}
fun traverse(node: TreeNode?) {
if (node == null) return
val left = maxOf(maxGain(node.left), 0)
val right = maxOf(maxGain(node.right), 0)
maxSum = maxOf(maxSum, node.`val` + left + right)
traverse(node.left)
traverse(node.right)
}
traverse(root)
return maxSum
}
}⭐ 2: 一次 DFS 同時計算貢獻與更新最大值#
- 概念: 遞迴計算每個節點的最大單邊貢獻,同時用「節點值 + 左貢獻 + 右貢獻」更新全域最大路徑和
- 時間複雜度:
O(n) - 空間複雜度:
O(h)
class Solution {
private var maxSum = Int.MIN_VALUE
fun maxPathSum(root: TreeNode?): Int {
maxSum = Int.MIN_VALUE
maxGain(root)
return maxSum
}
private fun maxGain(node: TreeNode?): Int {
if (node == null) return 0
val leftGain = maxOf(maxGain(node.left), 0)
val rightGain = maxOf(maxGain(node.right), 0)
// 經過此節點的完整路徑和
maxSum = maxOf(maxSum, node.`val` + leftGain + rightGain)
// 回傳給父節點的最大貢獻(只能選一邊)
return node.`val` + maxOf(leftGain, rightGain)
}
}為什麼回傳值只能選一邊?
路徑是一條線,不能分叉。如果一個節點回傳給父節點的貢獻同時包含左右子路徑,那加上父節點後路徑就會形成「T 字形」分叉,這不符合路徑的定義。
所以:
- 更新全域最大值時可以用
左 + 根 + 右(此路徑以該節點為頂點) - 回傳給父節點時只能用
根 + max(左, 右)(作為父節點路徑的一部分)
🔑 Takeaways#
- Pattern: 「回傳值」用於向上傳遞貢獻,「全域變數」用於追蹤最佳完整路徑 — 和 Diameter of Binary Tree 相同模式
- 關鍵技巧: 區分「完整路徑」和「單邊貢獻」是解決樹路徑問題的核心思維