題目描述(困難難度)
考慮一條路徑,可以從任意節點開始,每個節點最多經過一次,問經過的節點的和最大是多少。
解法一 遞歸
參考了 這裏。
首先看到二叉樹的題,肯定就是想遞歸了。遞歸常規的思路,肯定是遞歸考慮左子樹的最大值,遞歸考慮右子樹的最大值。
public int maxPathSum(TreeNode root) {
if (root == null) {
return Integer.MIN_VALUE;
}
//左子樹的最大值
int left = maxPathSum(root.left);
//右子樹的最大值
int right = maxPathSum(root.right);
//再考慮包含根節點的最大值
int all = ....;
return Math.max(Math.max(left, right), all);
}
問題就來了,怎麼考慮包含根節點的最大路徑等於多少?因爲我們遞歸求出來的最大 left
可能不包含根節點的左孩子,例如下邊的情況。
8
/ \
-3 7
/ \
1 4
左子樹的最大值 left
肯定就是 4
了,然而此時的根節點 8
並不能直接和 4
去相連。所以考慮包含根節點的路徑的最大值時,並不能單純的用 root.val + left + right
。
所以如果考慮包含當前根節點的 8
的最大路徑,首先必須包含左右孩子,其次每次遇到一個分叉,就要選擇能產生更大的值的路徑。例如下邊的例子:
8
/ \
-3 7
/ \
1 4
\ / \
3 2 6
考慮左子樹 -3 的路徑的時候,我們有左子樹 1 和右子樹 4 的選擇,但我們不能同時選擇
如果同時選了,路徑就是 ... -> 1 -> -3 -> 4 -> ... 就無法通過根節點 8 了
所以我們只能去求左子樹能返回的最大值,右子樹能返回的最大值,選一個較大的
假設我們只考慮通過根節點 8
的最大路徑是多少,那麼代碼就可以寫出來了。
public int maxPathSum(TreeNode root) {
//如果最大值是負數,我們選擇不選
int left = Math.max(helper(root.left), 0);
int right = Math.max(helper(root.right), 0);
return root.val + left + right;
}
int helper(TreeNode root) {
if (root == null) return 0;
int left = Math.max(helper(root.left), 0);
int right = Math.max(helper(root.right), 0);
//選擇左子樹和右子樹產生的值較大的一個
return root.val + Math.max(left, right);
}
接下來我覺得就是這道題最精彩的地方了,現在我們只考慮了包含最初根節點 8
的路徑。那如果不包含當前根節點,而是其他的路徑呢?
可以發現在 helper
函數中,我們每次都求了當前給定的節點的左子樹和右子樹的最大值,和我們 maxPathSum
函數的邏輯是一樣的。所以我們利用一個全局變量,在考慮 helper
函數中當前 root
的時候,同時去判斷一下包含當前 root
的路徑的最大值。
這樣在遞歸過程中就考慮了所有包含當前節點的情況。
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
helper(root);
return max;
}
int helper(TreeNode root) {
if (root == null) return 0;
int left = Math.max(helper(root.left), 0);
int right = Math.max(helper(root.right), 0);
//求的過程中考慮包含當前根節點的最大路徑
max = Math.max(max, root.val + left + right);
//只返回包含當前根節點和左子樹或者右子樹的路徑
return root.val + Math.max(left, right);
}
總
這道題最妙的地方就是在遞歸中利用全局變量,來更新最大路徑的值,太強了。前邊遇到過和全局變量結合的遞歸,例如 106 題,當遞歸和全局變量結合有時候確實會難理解些。而在 110 題 中也應用了和這個題一樣的思想,就是發現遞歸過程和主函數有一樣的邏輯,此時可以在遞歸過程中就可以進行求解。
更多詳細通俗題解詳見 leetcode.wang 。