Dynamic Programming学习笔记 (42) - 叶值的最小代价生成树 (力扣# 1130)

117 阅读2分钟

本题出自力扣题库第1130题。题面大意如下:

给定一个正整数数组arr,考虑所有满足以下条件的二叉树: 每个节点都有 0 个或是 2 个子节点。 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。 每个非叶节点的值等于其左子树和右子树中叶节点的最大值的乘积。 在所有这样的二叉树中,返回每个非叶节点的值的最小可能总和。

示例:

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

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

题解:

以数组长度为5作为例子,在树的根节点之下,我们有以下几种满足题目要求的组合

  • 左边4个叶节点,右边1个
  • 左边3个叶节点,右边2个
  • 左边2个叶节点,右边3个
  • 左边1个叶节点,右边4个

我们要做的是对这每一种组合,计算其每个非叶节点的值之和,并从中找到最小值。而对于每一个组合的计算, 又分为两个步骤,第一步是根节点的值,也就是左边所有叶节点的最大值乘以右边所有叶节点的最大值;第二步则是在根节点之下左子树和右子树中每个非叶节点的值之和的最下值。我们可以看到这里第二步的计算就是一左一右两个与原问题相同结构的递归。

在递归结构确定之后,我们再加上DP数组用于缓存计算结果以避免重复结果,这里有两个地方可以使用DP,分别对应上面的两个步骤。

Java代码如下:

class Solution {
    int[][] dpMax;
    int[][] dpSum;

    public int mctFromLeafValues(int[] arr) {
        int N = arr.length;

        if (N == 2) {
            return arr[0] * arr[1];
        }

        dpMax = new int[N][N];
        dpSum = new int[N][N];

        for (int i = 0; i < N; i ++) {
            dpMax[i][i] = arr[i];
            for (int j = i + 1; j < N; j ++) {
                dpMax[i][j] = Math.max(dpMax[i][j - 1], arr[j]);
            }
        }
        return helper(0, N - 1, arr);
    }

    private int helper(int start, int end, int[] arr) {
        if (dpSum[start][end] != 0) {
            return dpSum[start][end];
        }

        int minSum = Integer.MAX_VALUE;
        for (int i = start; i < end; i ++) {
            int sum = dpMax[start][i] * dpMax[i + 1][end];
            if (i > start) {
                sum += helper(start, i, arr);
            }
            if (i + 1 < end) {
                sum += helper(i + 1, end, arr);
            }
            minSum = Math.min(minSum, sum);
        }
        return dpSum[start][end] = minSum;
    }
}