叶值的最小代价生成树
难度:
标签:
题目描述
给你一个正整数数组 arr
,考虑所有满足以下条件的二叉树:
- 每个节点都有
0
个或是2
个子节点。 - 数组
arr
中的值与树的中序遍历中每个叶节点的值一一对应。 - 每个非叶节点的值等于其左子树和右子树中叶节点的最大值的乘积。
在所有这样的二叉树中,返回每个非叶节点的值的最小可能总和。这个和的值是一个 32 位整数。
如果一个节点有 0 个子节点,那么该节点为叶节点。
示例 1:

输入:arr = [6,2,4] 输出:32 解释:有两种可能的树,第一种的非叶节点的总和为 36 ,第二种非叶节点的总和为 32 。
示例 2:

输入:arr = [4,11] 输出:44
提示:
2 <= arr.length <= 40
1 <= arr[i] <= 15
- 答案保证是一个 32 位带符号整数,即小于
231
。
代码结果
运行时间: 29 ms, 内存: 16.1 MB
/*
* Solution for finding the minimum possible sum of the non-leaf node values in a binary tree
* using Java Streams. The solution is similar to the dynamic programming approach but uses
* streams for intermediate calculations.
*/
import java.util.Arrays;
import java.util.stream.IntStream;
public class SolutionStream {
public int mctFromLeafValues(int[] arr) {
int n = arr.length;
int[][] dp = new int[n][n];
int[][] max = new int[n][n];
// Fill the max array
IntStream.range(0, n).forEach(i -> {
max[i][i] = arr[i];
IntStream.range(i + 1, n).forEach(j -> max[i][j] = Math.max(max[i][j - 1], arr[j]));
});
// Fill the dp array
IntStream.range(2, n + 1).forEach(len -> {
IntStream.range(0, n - len + 1).forEach(i -> {
int j = i + len - 1;
dp[i][j] = IntStream.range(i, j)
.map(k -> dp[i][k] + dp[k + 1][j] + max[i][k] * max[k + 1][j])
.min().getAsInt();
});
});
return dp[0][n - 1];
}
}
解释
方法:
这个题解使用了一个单调栈的方法来寻求最小化非叶节点值的总和。核心思想是在遍历数组的过程中,通过维持一个单调递减的栈,确保每次计算的非叶节点值是局部最优的。栈中的每个元素都代表了一个潜在的叶子节点。当新的元素x大于栈顶元素时,栈顶元素将被弹出,并计算产生的非叶节点的值,这个值是弹出元素和其邻近的更大元素的乘积。这样可以保证在每个步骤中,选择的都是可能的最小的非叶节点值。最后,栈中剩余的元素将按照顺序继续计算,直到栈中仅剩一个元素。
时间复杂度:
O(n)
空间复杂度:
O(n)
代码细节讲解
🦆
在单调栈解法中,为什么在遇到新元素x大于栈顶元素时,需要弹出栈顶元素并计算非叶节点值?这个逻辑是如何最小化非叶节点总和的?
▷🦆
题解里提到,当弹出一个元素y,如果栈不为空且`stack[-1]`大于x时,计算的非叶节点值为`y*x`,否则为`y*stack[-1]`。这种处理方式的目的是什么?
▷🦆
在处理完整个数组后,题解中提到需要继续处理栈中剩余的元素。如果在栈中剩余多个元素,为什么总是选择栈顶两个元素进行计算,而不是考虑其他组合?
▷🦆
题解中提到的单调递减栈是如何确保每次计算的非叶节点值是局部最优的?这种局部最优如何贡献于全局最优解的形成?
▷