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; } };
這道題的邏輯並不複雜,仍是很是套路的內容。關鍵點就是在與要相通咱們拆分後的狀況到底是什麼,也就是分析中提到的拆分以後其中一部分必然是以當前節點爲首的子二叉樹。而剩下的就是套用深度優先遍歷去實現便可。
在咱們這麼多期的周賽題解以後,相信小夥伴們已經逐漸的開始熟悉這些套路了吧。忽然以爲小豬作的事情仍是有意義的呢,開心的搖了搖豬尾巴 >.<