先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
每週一題,代碼無敵~這一次,青銅三人行決定在五一假期期間挑戰一道難度爲「困難」的題目:
二叉樹中的最大路徑
青銅三人行——每週一題@二叉樹中的最大路徑和
力扣題目
給定一個非空二叉樹,返回其最大路徑和。
本題中,路徑被定義爲一條從樹中任意節點出發,達到任意節點的序列。該路徑至少包含一個節點,且不一定經過根節點。
示例 1:
輸入: [1,2,3]
1
/ \
2 3
輸出: 6
示例 2:
輸入: [-10,9,20,null,null,15,7]
-10
/ \
9 20
/ \
15 7
輸出: 42
解題思路
因爲這次題目相對來說比較困難,因此就以一種思路來說明。
這道題的難點在於,題目中要求取的“任意節點出發”的路徑,且不一定經過根節點。導致在如何迭代求取上,陷入了一個比較複雜的境地。
爲了求解這個問題,我們需要將題目先簡化一下,分步驟完成:
求取某一節點爲起始的最大路徑和
const maxChildrenPathValue = function (node) {
if (node == null) return 0;
let leftPathVal = maxChildrenPathValue(node.left);
let rightPathVal = maxChildrenPathValue(node.right);
const maxPathValue = Math.max(leftPathVal, rightPathVal) + node.val;
return Math.max(maxPathValue, 0);
}
在這一步中,我們遞歸求取了某一個節點爲開始的單邊最大路徑和,值的注意的是,如果取出來的值是負值,則設爲0,意爲「捨棄」掉這條路徑。
求取經過某一根節點的最大路徑和
完成了上一步,我們就可以求取經過某一特定根節點的最大路徑和了,即把「某個節點的值」與「左邊最大路徑和」和「右邊最大路徑和」相加:
const getRootMaxPathVal = function (root) {
const leftMaxPathVal = maxChildrenPathValue(root.left);
const rightMaxPathVal = maxChildrenPathValue(root.right);
return leftMaxPathVal + rightMaxPathVal + root.val;
}
遍歷求取整顆二叉樹的最大路徑值
有了上面的基礎,我們就可以遍歷整個二叉樹,來求取所有節點的最大路徑和,並取出其中的最大值來作爲整顆二叉樹的最大路徑和了,在這裏我們用了二叉樹前序遍歷,並使用了一個全局變量 result 來記錄最大值:
const preorderTraversal = function (root) {
if (root) {
const value = getRootMaxPathVal(root);
if(value > result) result = value;
preorderTraversal(root.left);
preorderTraversal(root.right);
}
}
到此我們就可以解出這道題目了,完整代碼如下:
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxPathSum = function (root) {
let result = -Infinity;
const maxChildrenPathValue = function (node) {
if (node == null) return 0;
let leftPathVal = maxChildrenPathValue(node.left);
let rightPathVal = maxChildrenPathValue(node.right);
const maxPathValue = Math.max(leftPathVal, rightPathVal) + node.val;
return Math.max(maxPathValue, 0);
}
const getRootMaxPathVal = function (root) {
const leftMaxPathVal = maxChildrenPathValue(root.left);
const rightMaxPathVal = maxChildrenPathValue(root.right);
return leftMaxPathVal + rightMaxPathVal + root.val;
}
const preorderTraversal = function (root) {
if (root) {
const value = getRootMaxPathVal(root);
if(value > result) result = value;
preorderTraversal(root.left);
preorderTraversal(root.right);
}
}
preorderTraversal(root);
return result;
};
優化
同樣的解題思路下,helen 發現到在求取某一節點爲起始的最大路徑和這一步的時候,已經在對二叉樹進行遍歷了,那能不能直接在一次遞歸遍歷中解出題目呢?helen 對代碼進行了優化:
var maxPathSum = function(root) {
let max_sum = -Infinity;
function max_gain (root) {
if (root === null) {
return 0;
}
const left_gain = Math.max(max_gain(root.left), 0);
const right_gain = Math.max(max_gain(root.right), 0);
const newPath = root.val + left_gain + right_gain;
max_sum = Math.max(newPath, max_sum);
return root.val + Math.max(left_gain, right_gain);
}
max_gain(root);
return max_sum;
};
代碼簡潔多了,運行也更快了!你有沒有發現兩個解法的共同之處和不同之處呢?
extra
最後依然是曾大師的 go 語言 show time~
func maxPathSum(root *TreeNode) int {
var val = INT_MIN();
subMaxPathSum(root, &val);
return val;
}
func subMaxPathSum(root *TreeNode,val *int) int{
if (root == nil){
return 0;
}
left := subMaxPathSum(root.Left, val);
right := subMaxPathSum(root.Right, val);
threeSub := root.Val + max(0, left) + max(0, right);
twoSub := root.Val + max(0, max(left, right));
*val = max(*val, max(threeSub, twoSub));
return twoSub;
}
func INT_MIN() int{
const intMax = int(^uint(0) >> 1)
return ^intMax
}
func max(x, y int) int {
if x < y {
return y
}
return x
}
結果依然很驚人啊。。。嗯。。。
最後
這次的題目有些複雜,但通過簡化題目、拆解步驟,也可以讓困難的題目得到解決。而日常編程的過程中,也是在將複雜問題簡單化、步驟化的一個過程。最後留個小問題,之前提到過,所有的遞歸都可以用循環來解決,那麼在第一步的遞歸中,如果用循環解決該怎麼做呢?
下週見~
三人行