[LeetCode] 1130. Minimum Cost Tree From Leaf Values 葉值的最小代價生成樹



Given an array arr of positive integers, consider all binary trees such that:

  • Each node has either 0 or 2 children;
  • The values of arr correspond to the values of each leaf in an in-order traversal of the tree.  (Recall that a node is a leaf if and only if it has 0 children.)
  • The value of each non-leaf node is equal to the product of the largest leaf value in its left and right subtree respectively.

Among all possible binary trees considered, return the smallest possible sum of the values of each non-leaf node.  It is guaranteed this sum fits into a 32-bit integer.

Example 1:

Input: arr = [6,2,4]
Output: 32
Explanation:
There are two possible trees.  The first has non-leaf node sum 36, and the second has non-leaf node sum 32.

    24            24
   /  \          /\
  12   4        6    8
 /  \               /\
6    2             2   4

Constraints:

  • 2 <= arr.length <= 40
  • 1 <= arr[i] <= 15
  • It is guaranteed that the answer fits into a 32-bit signed integer (ie. it is less than 2^31).

這道題給了一個數組,說是里面都是一棵樹的葉結點,說是其組成的樹是一棵滿二叉樹,且這些葉結點值是通過中序遍歷得到的,樹中的非葉結點值是是其左右子樹中最大的兩個葉結點值的乘積,滿足這些條件的二叉樹可能不止一個,現在讓找出非葉結點值之和最小的那棵樹,並返回這個最小值。這道題的要求挺多的,好在給了一個帶圖的例子,可以幫助我們理解,通過觀察例子,可以發現葉結點值 6,2,4 的順序是不能變的,但是其組合方式可能很多,若有很多個葉結點,那么其組合方式就非常的多了。題目中給的提示是用動態規划 Dynamic Programming 來做,用一個二維的 dp 數組,其中 dp[i][j] 表示在區間 [i, j] 內的子數組組成的二叉樹得到非葉結點值之和的最小值,接下來想狀態轉移方程怎么寫。首先,若只有一個葉結點的話,是沒法形成非葉結點的,所以 dp[i][i] 是0,最少得有兩個葉結點,才有非0的值,即 dp[i][i+1] = arr[i] * arr[i+1],而一旦區間再大一些,就要遍歷其中所有的小區間的情況,用其中的最小值來更新大區間的 dp 值。這種按區間長度順序來更新的方法在之前的題目中也出現過,比如 Burst BalloonsRemove Boxes。這里的區間長度從1到n,長度為1,表示至少有兩個葉結點,i從0遍歷到 n-len,j可以直接確定出來為 i+len,然后用k來將區間 [i, j] 分為兩個部分,由於分開的小區間在之前都已經更新過了,所以其 dp 值可以直接得到,然后再加上這兩個區間中各自的最大結點值的乘積。為了不每次都遍歷小區間來獲得最大值,可以提前計算好任意區間的最大值,保存在 maxVec 中,這樣就可以快速獲取了,最后返回的結果保存在 dp[0][n-1] 中,參見代碼如下:


解法一:

class Solution {
public:
    int mctFromLeafValues(vector<int>& arr) {
        int n = arr.size();
        vector<vector<int>> dp(n, vector<int>(n));
        vector<vector<int>> maxVec(n, vector<int>(n));
        for (int i = 0; i < n; ++i) {
            int curMax = 0;
            for (int j = i; j < n; ++j) {
                curMax = max(curMax, arr[j]);
                maxVec[i][j] = curMax;
            }
        }
        for (int len = 1; len < n; ++len) {
            for (int i = 0; i + len < n; ++i) {
                int j = i + len;
                dp[i][j] = INT_MAX;
                for (int k = i; k < j; ++k) {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + maxVec[i][k] * maxVec[k + 1][j]);
                }
            }
        }
        return dp[0][n - 1];
    }
};

下面的這種解法是參見了大神 lee215 的帖子,是一種利用單調棧來解的方法,將時間復雜度優化到了線性,驚為天人。思路是這樣的,當兩個葉結點生成一個父結點值,較小的那個數字使用過一次之后就不再被使用了,因為之后形成的結點是要子樹中最大的那個結點值。所以問題實際上可以轉化為在一個數組中,每次選擇兩個相鄰的數字a和b,移除較小的那個數字,代價是 a*b,問當移除到數組只剩下一個數字的最小的代價。Exactly same problem,所以b是有可能復用的,要盡可能的 minimize,數字a可以是一個局部最小值,那么b就是a兩邊的那個較小的數字,這里使用一個單調棧來做是比較方便的。關於單調棧,博主之前也有寫過一篇總結 LeetCode Monotonous Stack Summary 單調棧小結,在 LeetCode 中的應用也非常多,是一種必須要掌握的方法。這里維護一個最小棧,當前棧頂的元素是最小的,一旦遍歷到一個較大的數字,此時當前棧頂的元素其實是一個局部最小值,它就需要跟旁邊的一個較小的值組成一個左右葉結點,這樣形成的父結點才是最小的,然后將較小的那個數字移除,符合上面的分析。然后繼續比較新的棧頂元素,若還是小,則繼續相同的操作,否則退出循環,將當前的數字壓入棧中。最后若棧中還有數字剩余,則一定是從大到小的,只需將其按順序兩兩相乘即可,參見代碼如下:


解法二:

class Solution {
public:
    int mctFromLeafValues(vector<int>& arr) {
        int res = 0, n = arr.size();
        vector<int> st{INT_MAX};
        for (int num : arr) {
            while (!st.empty() && st.back() <= num) {
                int mid = st.back();
                st.pop_back();
                res += mid * min(st.back(), num);
            }
            st.push_back(num);
        }
        for (int i = 2; i < st.size(); ++i) {
            res += st[i] * st[i - 1];
        }
        return res;
    }
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/1130


類似題目:

Burst Balloons

Remove Boxes

Sum of Subarray Minimums

Online Stock Span

Score of Parentheses

Next Greater Element II

Next Greater Element I

Largest Rectangle in Histogram

Trapping Rain Water


參考資料:

https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/

https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/discuss/340027/Java-DP-easy-to-understand

https://leetcode.com/problems/minimum-cost-tree-from-leaf-values/discuss/339959/One-Pass-O(N)-Time-and-Space


LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM