青铜三人行之二叉树中的最大路径和

先说一个消息,为了方便互相交流学习,青铜三人行建了个微信群,感兴趣的伙伴可以扫码加下面的小助手抱你入群哦!
青铜三人行小助手

每周一题,代码无敌~这一次,青铜三人行决定在五一假期期间挑战一道难度为「困难」的题目:

二叉树中的最大路径

青铜三人行——每周一题@二叉树中的最大路径和

力扣题目

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

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

结果依然很惊人啊。。。嗯。。。
在这里插入图片描述

最后

这次的题目有些复杂,但通过简化题目、拆解步骤,也可以让困难的题目得到解决。而日常编程的过程中,也是在将复杂问题简单化、步骤化的一个过程。最后留个小问题,之前提到过,所有的递归都可以用循环来解决,那么在第一步的递归中,如果用循环解决该怎么做呢?
下周见~


三人行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值