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.
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
).
题意:
给一个正整数数组 arr,考虑所有满足以下条件的二叉树:
- 每个节点都有 0 个或是 2 个子节点。
- 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。(一个节点有 0 个子节点,那么该节点为叶节点。)
- 每个非叶节点的值等于其左子树和右子树中叶节点的最大值的乘积。
在所有这样的二叉树中,返回一个最小和值(每个非叶节点的值相加)。这个和的值是一个 32 位整数。
类型:区间型的DP。
令状态dp[ i ][ j ]表示将[i, j]之间的元素最终聚成一个元素所需要的cost。显然,突破点就是如何将这个区间划分为左右两个分支,这就需要遍历可能的内部分界点k。这样的话,就可以得到状态转移方程:
dp[i][j] = min{dp[i][k] + dp[k+1][j] + largest[i][k] * largest[k+1][j]}
public int mctFromLeafValues(int[] arr) {
int n = arr.length;
int[][] dp = new int[n][n];
for (int[] sub: dp) Arrays.fill(sub, Integer.MAX_VALUE);
int[][] large = new int[n][n];
for (int[] sub: large) Arrays.fill(sub, Integer.MAX_VALUE);
int sum = 0;
for (int i = 0; i < n; i++) {
dp[i][i] = arr[i];
large[i][i] = arr[i];
sum += arr[i];
}
for (int len = 2; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
for (int k = i; k < j; k++) {
dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j] + large[i][k] * large[k + 1][j]);
large[i][j] = Math.max(large[i][k], large[k + 1][j]);
}
}
}
return dp[0][n - 1] - sum;
}
单调栈解法:
题目要求使乘积的和最小,贪心为选择节点两端的最小的节点合并为一颗子树,构建价值为两者的乘积,使用单调递减栈来进行遍历。
O(n)
根据题意我们知道,每次两个相邻元素组成一棵子树后,会将较小的元素删去,留下较大的元素。所以目标就是每次删除局部最小的那个元素。比如[6,2,4]中2就是局部最小因为他小于左右两边的元素。我们将局部最小的元素和两边较小的元素相乘加入答案,同时将这个局部最小的元素抹去。如:
[6,2,4,8] res = 0
[6,4,8] res = 8
[6,8] res = 8 + 24
[8] res = 8 + 24 + 48
考虑使用一个单调递减栈来存储数组元素,如果当前元素小于等于栈顶元素直接入栈;否则,说明当前栈顶元素是一个局部最小值,我们用这个局部最小值的两端较小与栈顶元素相乘,同时将栈顶元素出栈,重复上述操作,直至栈顶元素不是局部最小值,再将当前元素入栈。处理完整个数组后,如果栈中还有多的元素,我们从栈顶依次往下乘加入答案即可。
这里为了处理边界情况,先在栈中插入了一个INT_MAX。
时间复杂度:每个元素入栈一次,出栈一次。所以总的时间复杂度是O(n)。
public int mctFromLeafValues1(int[] arr) {
Stack<Integer> st = new Stack<>();
st.push(Integer.MAX_VALUE);
int ans = 0;
for (int i = 0; i < arr.length; i++) {
while (st.peek() < arr[i]) {
int t = st.peek();
st.pop();
ans += t * Math.min(st.peek(), arr[i]);
}
st.push(arr[i]);
}
while (st.size() > 2) {
int t = st.peek();
st.pop();
ans += t * st.peek();
}
return ans;
}
https://www.jianshu.com/p/71f5d49292fc
https://github.com/wisdompeak/LeetCode/tree/master/Dynamic_Programming/1130.Minimum-Cost-Tree-From-Leaf-Values
https://www.acwing.com/solution/LeetCode/content/3996/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。