青銅三人行之二叉樹中的最大路徑和

先說一個消息,爲了方便互相交流學習,青銅三人行建了個微信羣,感興趣的夥伴可以掃碼加下面的小助手抱你入羣哦!
青銅三人行小助手

每週一題,代碼無敵~這一次,青銅三人行決定在五一假期期間挑戰一道難度爲「困難」的題目:

二叉樹中的最大路徑

青銅三人行——每週一題@二叉樹中的最大路徑和

力扣題目

給定一個非空二叉樹,返回其最大路徑和。

本題中,路徑被定義爲一條從樹中任意節點出發,達到任意節點的序列。該路徑至少包含一個節點,且不一定經過根節點。

示例 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
}

結果依然很驚人啊。。。嗯。。。
在這裏插入圖片描述

最後

這次的題目有些複雜,但通過簡化題目、拆解步驟,也可以讓困難的題目得到解決。而日常編程的過程中,也是在將複雜問題簡單化、步驟化的一個過程。最後留個小問題,之前提到過,所有的遞歸都可以用循環來解決,那麼在第一步的遞歸中,如果用循環解決該怎麼做呢?
下週見~


三人行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章