1130 叶值的最小代价生成树(dfs、动态规划)

265 篇文章 5 订阅
96 篇文章 2 订阅

1. 问题描述:

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

示例:

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-cost-tree-from-leaf-values

提示:

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

2. 思路分析:

① 分析题目可以知道这道题题目有一个明显的特点:需要尝试所有的可能性才可以得到最终的结果,我们可以尝试将列表划分为两棵子树,分别求解出左右子树非叶子节点的最小和和叶子节点的最大值,划分的范围不一样那么得到的结果也是不一样的,所以可以使用dfs搜索来解决,dfs搜索的话可以尝试所有的可能性并将最终的结果返回,我们可以将列表划分为两个范围,代表着左右子树,分别递归左右子树,求解出满足题目要求的答案即可,对于每一个节点的左右子树都是使用同样的方法进行求解所以这也是能够使用递归求解的一个条件

② 除了使用递归来解决,领扣的题解中使用的比较多的是动态规划的思路来解决的,dp[i][j]的含义是从i到j范围构成树的最小的代价,从题目中可以分析得到状态转移方程:

具体可以使用三层循环来解决,第一层循环是左右子树的长度范围,第二层循环是左右子树的起点,第三层循环表示这个范围内划分的左右子树,结合上面的状态转移方程也比较好理解

3. 代码如下:

dfs:(dfs需要添加上@lru_cache(maxsize=None)才可以通过)

import sys
from typing import List
from functools import lru_cache


class Solution:
    def mctFromLeafValues(self, arr: List[int]) -> int:
        # dfs
        @lru_cache(maxsize=None)
        def dfs(l: int, r: int):
            # 叶子节点
            if l == r: return 0, arr[l]
            minsum, maxchild = sys.maxsize, sys.maxsize
            for i in range(l, r):
                # 左子树的非叶子节点之和以及左子树叶子节点的最大值
                lsum, lchild = dfs(l, i)
                # 右子树的非叶子节点之和以及右子树叶子节点的最大值
                rsum, rchild = dfs(i + 1, r)
                cur = lsum + rsum + lchild * rchild
                if cur < minsum:
                    minsum = cur
                    maxchild = max(lchild, rchild)
            return minsum, maxchild
        return dfs(0, len(arr) - 1)[0]

领扣中大佬写的动态规划代码,题解:https://leetcode-cn.com/problems/minimum-cost-tree-from-leaf-values/solution/dong-tai-gui-hua-dan-diao-zhan-python3-by-smoon1-2/

class Solution:
    def mctFromLeafValues(self, arr: List[int]) -> int:
        n = len(arr)
        # 初始值设为最大
        dp = [[float('inf') for _ in range(n)] for _ in range(n)]
        # 初始区间查询最大值设为0
        maxval = [[0 for _ in range(n)] for _ in range(n)]
        # 求区间[i, j]中最大元素
        for i in range(n):
            for j in range(i, n):
                maxval[i][j] = max(arr[i:j + 1])
        # 叶子结点不参与计算
        for i in range(n):
            dp[i][i] = 0
        # 枚举区间长度
        for l in range(1, n):
            # 枚举区间起始点
            for s in range(n - l):
                # 枚举划分两棵子树
                for k in range(s, s + l):
                    dp[s][s + l] = min(dp[s][s + l], dp[s][k] + dp[k + 1][s + l] + maxval[s][k] * maxval[k + 1][s + l])
        return dp[0][n - 1]

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值