Hi 你们好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 leetcode 周赛题解。node
这里是第 174 期的第 3 题,也是题目列表中的第 1339 题 -- 『分裂二叉树的最大乘积』git
给你一棵二叉树,它的根为 root
。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽量大。github
因为答案可能会很大,请你将结果对 10^9 + 7 取模后再返回。shell
示例 1:segmentfault
输入:root = [1,2,3,4,5,6] 输出:110 解释:删除红色的边,获得 2 棵子树,和分别为 11 和 10 。它们的乘积是 110 (11*10)
示例 2:优化
输入:root = [1,null,2,3,4,null,null,5,6] 输出:90 解释:移除红色的边,获得 2 棵子树,和分别是 15 和 6 。它们的乘积为 90 (15*6)
示例 3:spa
输入:root = [2,3,9,10,7,8,6,5,4,11,1] 输出:1025
示例 4:3d
输入:root = [1,1] 输出:1
提示:code
50000
个节点,且至少有 2
个节点。[1, 10000]
之间。MEDIUMblog
题目提供了一棵二叉树的根节点,须要咱们删除一条边使得这棵二叉树变成两棵二叉树。而要求就是对这两棵二叉树里各个节点分别求和以后,这两个和的乘积最小。
不知道小伙伴们是什么想法,不太小猪看完题目以后第一反应是,先遍历起来。毕竟,只对着一个根节点的话,再好的戏也出不来。而提到遍历二叉树的话,又想少写一点代码,那小猪的第一反应就是用递归来实现深度优先遍历啦。
接下来回到题目的需求上,咱们须要找到拆分后两个和的乘积最大。因为这棵树给定后就不会变了,因此全部节点求和的值实际上是必定的。而对于这个肯定的值,咱们把它拆成两个加数后要使得它们的乘积最大,那么这两个数应该尽量的靠近总和的二分之一。相信到这里,不少小伙伴都是能想到哒。不过再日后,若是直接用数学的方式来处理这个问题,小猪就想不明白了。小猪苯苯的,但愿小伙们能帮帮我。
那没有了数学的方法,小猪就只能用计算机的方法啦。咱们能够想象一下,在去掉一条边以后,拆分红的两棵二叉树,其中必然有一棵的顶点位于咱们刚才断掉的那条边的下游。也就是说,从当前位置拆开以后,其实分红的两部分,就是以当前节点为顶点的二叉树,以及其余的节点。
想通了这一点以后,再结合前面提到的总和是固定不变的。那么咱们的思路也就大概清晰啦。
咱们能够先计算出全部节点的总和。而后对于每个节点,咱们也能算出以它为顶点的子二叉树的总和。那么剩下的那一部分也就知道啦。在对每一个可能的节点都进行这样的计算以后,咱们就能找到那个最大的乘积了。
基于这个思路,咱们具体的流程以下:
基于这个流程,咱们能够实现相似下面的代码:
const maxProduct = root => { let sum = max = 0; sum = helper(root); helper(root); return max % (10 ** 9 + 7); function helper(node) { const subSum = node.val + (node.left ? helper(node.left) : 0) + (node.right ? helper(node.right) : 0); sum && subSum * (sum - subSum) > max && (max = subSum * (sum - subSum)); return subSum; } };
上面的代码咱们对整个二叉树进行了两次遍历,其中第一次是为了计算总和,而第二次则是进行每一个节点的子二叉树的计算和判断。
不知道小伙伴们有没有发现,其实咱们第二次遍历中所计算的东西,在第一次遍历的时候也是能够获得的。而惟一最初没法计算的,就是与总和的差值作乘积的结果。那么咱们其实能够把前面的内容都记录下来,在获得了总和以后直接进行计算便可。
基于这个优化思路,咱们具体的流程以下:
基于这个流程,咱们能够实现相似下面的代码:
const maxProduct = root => { const subSums = new Uint32Array(50000); let max = idx = 0; const sum = helper(root); for (let i = 0; i < idx; ++i) { const val = subSums[i]; val * (sum - val) > max && (max = val * (sum - val)); } return max % (10 ** 9 + 7); function helper(node) { const subSum = node.val + (node.left ? helper(node.left) : 0) + (node.right ? helper(node.right) : 0); subSums[idx++] = subSum; return subSum; } };
这道题的逻辑并不复杂,仍是很是套路的内容。关键点就是在与要相通咱们拆分后的状况到底是什么,也就是分析中提到的拆分以后其中一部分必然是以当前节点为首的子二叉树。而剩下的就是套用深度优先遍历去实现便可。
在咱们这么多期的周赛题解以后,相信小伙伴们已经逐渐的开始熟悉这些套路了吧。忽然以为小猪作的事情仍是有意义的呢,开心的摇了摇猪尾巴 >.<