算法初探LeetCode-叶值的最小代价生成树

236 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情

LeetCode1130:叶值的最小代价生成树

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

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

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

示例:

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

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

 

提示:

  • 2 <= arr.length <= 40
  • 1 <= arr[i] <= 15
  • 答案保证是一个 32 位带符号整数,即小于 2312^{31}

思路分析

  • 回忆中序序列,对于序列中的某一个元素,它左边的全部元素在这个元素的左子树中,它右边的全部元素在这个元素的右子树中。
  • 根据题意,给出的中序序列的所有元素都是叶节点。我们可以隐含地将序列分为左右两边,左半部分是左子树,右半部分是右子树。
  • 我们可以递归地求解最小和,即定义一个方法getMinSum,让它返回可以得到的最小和。因为我们将序列分为左右两边,它们分别是左右子树,所以只要在两边分别找出最大元素就可以计算当前节点的值。
  • 为了避免重复计算,设置备忘录记录一定范围内序列可能得到的最小和。

算法代码

public int mctFromLeafValues(int[] arr) {
    if (arr == null || arr.length == 0)
        return 0;
    int n = arr.length;
    memoization = new int[n][n];
    return getMinSum(arr, 0, arr.length - 1);
}

private int[][] memoization;

private int getMinSum(int[] arr, int left, int right) {
    if (left == right) {
        return 0;
    }
    if (memoization[left][right] != 0)
        return memoization[left][right];
    int minSum = Integer.MAX_VALUE;
    for (int i = left; i < right; i++) {
        int leftStart = left, leftEnd = i, rightStart = i + 1, rightEnd = right;
        int temp = getMinSum(arr, leftStart, leftEnd) + getMinSum(arr, rightStart, rightEnd) +
            getMaxNum(arr, leftStart, leftEnd) * getMaxNum(arr, rightStart, rightEnd);
        minSum = Math.min(temp, minSum);
    }
    memoization[left][right] = minSum;
    return minSum;
}

private int getMaxNum(int[] arr, int left, int right) {
    int max = Integer.MIN_VALUE;
    for (int i = left; i <= right; i++) {
        max = Math.max(max, arr[i]);
    }
    return max;
}

结果详情

Snipaste_2023-02-16_22-40-42.png

算法复杂度

  • 空间复杂度:O(1)O(1)
  • 时间复杂度:O(n)O(n)

掘金(JUEJIN)一起进步,一起成长!