1130. 叶值的最小代价生成树

190 阅读2分钟

给你一个正整数数组 arr,考虑所有满足以下条件的二叉树:

  • 每个节点都有 0 个或是 2 个子节点。
  • 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。(知识回顾:如果一个节点有 0 个子节点,那么该节点为叶节点。)
  • 每个非叶节点的值等于其左子树和右子树中叶节点的最大值的乘积。

在所有这样的二叉树中,返回每个非叶节点的值的最小可能总和。这个和的值是一个 32 位整数。

示例:

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

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

题解 :

/**
 * @param {number[]} arr
 * @return {number}
 */
// 方法一:单调栈,极小值,归纳,贪心
// 这个题实际上是将数组中相邻的数两两合并,计算他们的乘积之和,求最小的乘积之和。
// 合并相邻的两个数之后得到的是较大的一个数。
// 例如:
// 对于[6, 2, 4, 8],假设乘积之和为 res = 0;
// 可以将 2 与 4 合并,变为[6, 4, 8],res = 0 + 8 = 8;
// 然后将 6 与 4 合并,变为[6, 8],res = 8 + 24 = 32;
// 最后将 6 与 8 合并,变为[8],res = 32 + 48 = 80。
// 第一轮:arr[i] = 6 stack = [6] res = 0;
// 第二轮:arr[i] = 2 stack = [6, 2] res = 0;
// 第三轮:arr[i] = 4 stack = [6, 4] 栈顶值小于当前值 2 与 4 合并 res = 8
// 第四轮:arr[i] = 8 stack = [6] 栈顶值小于当前值 4 与 6 合并 res = 24 + 8 = 32
//         arr[i] = 8 stack = [8] 栈顶值小于当前值 6 与 8 合并 res = 48 + 32 = 80

// 例如:
// 对于[3, 2, 4],假设乘积之和为 res = 0;
// 可以将 3 与 2 合并,变为[3, 4],res = 0 + 6 = 6;
// 然后将 3 与 4 合并,变为[4],res = 6 + 12 = 18;
// 我们需要思考的就是如何合并,才能使得乘积之和 res 最小
var mctFromLeafValues = function (arr) {
    const n = arr.length;
    const stack = [];
    let res = 0;
    for (let i = 0; i < n; i++) {
        while (stack.length && (stack[stack.length - 1] <= arr[i] || i === n - 1)) {
            const bottom = stack.pop();
            if (stack.length) {
                res += Math.min(stack[stack.length - 1], arr[i]) * bottom;
            } else {
                res += bottom * arr[i];
            }
            // 单调递减时处理合并相邻两个数据之后得到最大的值
            arr[i] = Math.max(bottom, arr[i]);
        }
        stack.push(arr[i]);
    }
    return res;
};
// 方法二:动态规划
// 定义状态:
// dp[i][j] 表示将 arr[i...j]合并之后所得的最小乘积之和。
// dp[0][len] 就是最终答案。

// 状态转移方程:
// dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k + 1][j] + maxVal[i][k] * maxVal[k + 1][j])

// 这里的 maxVal[i][j] 代表从 i 到 j 的区间,最大的元素是 maxVal[i][j]。
// 注意:这里的 dp[i][j] 是最终的和,最终的最小乘积之和,需要将 dp[i][k] 与 dp[k + 1][j] 的和加上。

// 我们可以将 arr[i...j]分开,分为 arr[i...k]和 arr[k + 1...j],
// arr[i...k]和 arr[k + 1...j] 这两个区间最终可以合并为两个数,再将这两个数合并。
// 我们需要不断枚举 k,使得得到的 dp[i][j] 最小。

var mctFromLeafValues = function (arr) {
    const n = arr.length;
    const dp = new Array(n).fill(0).map(() => new Array(n).fill(0));
    const maxVal = new Array(n).fill(0).map(() => new Array(n).fill(0));

    for (let i = 0; i < n; i++) {
        maxVal[i][i] = arr[i];
        for (let j = i + 1; j < n; j++) {
            maxVal[i][j] = Math.max(maxVal[i][j - 1], arr[j]);
        }
    }

    for (let len = 1; len < n; len++) {
        for (let i = 0; i + len < n; i++) {
            dp[i][i + len] = Number.MAX_SAFE_INTEGER;
            for (let k = i; k < i + len; k++) {
                dp[i][i + len] = Math.min(
                    dp[i][i + len], 
                    dp[i][k] + dp[k + 1][i + len] + maxVal[i][k] * maxVal[k + 1][i + len]);
            }
        }
    }

    return dp[0][n - 1];
};

来源:力扣(LeetCode)

链接:leetcode.cn/problems/mi…

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。