【单调栈】1130. 叶值的最小代价生成树

news2024/11/18 5:59:58

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

难度:中等
力扣地址:https://leetcode.cn/problems/minimum-cost-tree-from-leaf-values/description/

题目内容

给你一个正整数数组 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 位带符号整数,即小于 2 31 2^{31} 231

单调栈的应用

题目中有提到过 非叶子结点的值等于它左子树最大值乘以右子树最大值

所以能否考虑类似于 “降维” 的思路,比如我们已经构建了一个子树,是否能把这个子树当做整科树的一个结点来处理,不需要考虑它的叶子是什么。

比如,原来的树结构转换后不考虑它的叶子结点,结果是一样的,即 30 + 18 + 10
在这里插入图片描述
结论 1:如果已经确定子树,可使用子树的根结点值代替原来的子树(注意转换后不能当作叶子结点)

得出这个结论的目的,是希望我们能自底向上的构建树结构,并且能够简化构建过程中我们存储的中间结果。

接下来的问题转换为,我们 如何构建子树

题目的意思是,希望构建叶值的最小代价生成树,并且根据题目的计算逻辑,叶子结点决定它的父结点的值(左右结点最大值的乘积),所以我们应该很容易想到,数值越大的叶子结点应当尽可能靠近根结点 —— 尽可能减少它参与祖辈结点的生成过程

结论2:数值越大的叶子结点应当尽可能靠近根结点。

下面是几个例子,印证这个观点。

在这里插入图片描述
在这里插入图片描述
到目前为止,离解开本题只有 一步之遥 —— 如何利用栈并利用结论 1 与结论 2 来构建目标树,并返回最终结果。

解决方法:利用栈的 “先进后出” 特点,将叶子值最大的压箱底,把比它小的根据条件出栈,并生成可用的子树根结点。

问题 1 什么时候入栈,什么时候出栈
回答: 我们需要构建一个严格的单调递减的栈,如果新来的元素破坏了这个规则,则开始出栈,并给新来的元素一个合适的位置;如果新来的元素满足单调递减的规则,则入栈这个元素。比如原有
[3, 2] 新来的元素是 5,明显破坏了单调性规则,需要出栈;比如新来的元素是 1,没有破坏规则,入栈。

问题 2 入栈出栈动作需要做什么
回答:如果只是入栈,则什么都不做;如果是出栈,则需要考虑开始构建子树。

问题3 出栈时,如何构建子树
回答:前面结论1提到,我们不关心子树的具体构成,只是需要生成后的根结点,以及它的左右子树的最大值。因此,出栈构建子树时,需要记录这个子树的所有结点中的最大值。比如 [3, 2] 构建子树,那么它的根结点的值为 6,它子树最大值为 3。

问题 4 如果输入数据是单调递减的,最后怎么出栈
回答:最后从栈顶向下依次出栈,并逐个结点的构建子树。比如 [4, 3, 2, 1] ,首先 21 构建生成子树 T1,然后让 3T1 结合生成子树 T2,最后 4T2 结合,生成最终树。

问题5 题目中提到的最小代价如何计算
回答:假设我们最终结果为 result,初始化为 0,生成子树的过程,计算得到根结点的值 r1,那么可以明确这个 r1 会参与最后 result 的计算,所以 result += r1,然后记录这个子树的最大值,因为最后根结点的值等于左右子树的最大值的乘积。记录这个子树的最大值的方法,是将这个子树的左右结点最大值入栈,随着出栈的过程派上用场。

基于单调栈计算最小代价生成树例子

例 1 [6, 2, 3, 4]

在这里插入图片描述
在这里插入图片描述

例 2 [6, 2, 3, 1]

在这里插入图片描述

在这里插入图片描述

例 3 [6, 2, 4, 1, 5]

在这里插入图片描述
在这里插入图片描述

例 4 [3, 6, 2, 5]

在这里插入图片描述
在这里插入图片描述

代码实现

如果使用的是其他语言,也可以参考一下这个 c++ 的实现代码,没有用到 c ++ 什么新特性,理解容易。

class Solution {
public:
    int mctFromLeafValues(vector<int>& arr) {
         // 用于存储节点值的栈,如果子树已经生成,栈内存储的是子树的左右子树各叶子结点的最大值
        stack<int> maxVals;
        // 计算最小代价
        int result = 0;
        for (int value : arr) {
            // 检查是否满足出栈条件
            while (!maxVals.empty() && maxVals.top() <= value) {
                // 要出栈的元素
                int top = maxVals.top();
                // 出栈
                maxVals.pop();
                // 如果栈为空,或者当前值小于栈顶元素(入栈不会破坏单调递减的约定)
                // 则将刚刚出栈的元素,与新来的值 value 生成子树
                // 并计算这个子树的根结点结果
                if (maxVals.empty() || maxVals.top() > value) {
                    result += (top * value);
                } else {
                    // 如果栈不为空,并且栈顶元素小于新来的 value,说明 value 不能简单入栈
                    // 需要挪到期望的位置,所以这个 value 目前不参与子树的构建
                    // 而是将刚刚出栈的,和目前的 top (将要出栈的)结合,生成子树
                    // 可以考虑结合本博客中的例 4 进行理解(步骤4)
                    result += (maxVals.top() * top);
                }
            }
            maxVals.push(value);
        }

        // 还存在未生成树的叶子结点或者子树的根结点(记录的是子树的最大值)
        // 则需要将剩下的出栈,并生成最终的树
        while (maxVals.size() >= 2) {
            int top = maxVals.top();
            maxVals.pop();
            result += top * maxVals.top();
        }
        return result;
    }
};

对应的 python 实现是

class Solution:
    def mctFromLeafValues(self, arr):
        # 用于存储节点值的栈,如果子树已经生成,栈内存储的是子树的左右子树各叶子结点的最大值
        maxVals = []
        # 计算最小代价
        result = 0
        
        for value in arr:
            # 检查是否满足出栈条件
            while maxVals and maxVals[-1] <= value:
                # 要出栈的元素
                top = maxVals.pop()
                # 如果栈为空,或者当前值小于栈顶元素(入栈不会破坏单调递减的约定)
                # 则将刚刚出栈的元素,与新来的值 value 生成子树
                # 并计算这个子树的根结点结果
                if not maxVals or maxVals[-1] > value:
                    result += top * value
                else:
                    # 如果栈不为空,并且栈顶元素小于新来的 value,说明 value 不能简单入栈
                    # 需要挪到期望的位置,所以这个 value 目前不参与子树的构建
                    # 而是将刚刚出栈的,和目前的 top (将要出栈的)结合,生成子树
                    result += maxVals[-1] * top
            
            maxVals.append(value)
        
        # 还存在未生成树的叶子结点或者子树的根结点(记录的是子树的最大值)
        # 则需要将剩下的出栈,并生成最终的树
        while len(maxVals) >= 2:
            top = maxVals.pop()
            result += top * maxVals[-1]
        
        return result

对应的 java 实现为

import java.util.Stack;

class Solution {
    public int mctFromLeafValues(int[] arr) {
        // 用于存储节点值的栈,如果子树已经生成,栈内存储的是子树的左右子树各叶子结点的最大值
        Stack<Integer> maxVals = new Stack<>();
        // 计算最小代价
        int result = 0;
        
        for (int value : arr) {
            // 检查是否满足出栈条件
            while (!maxVals.isEmpty() && maxVals.peek() <= value) {
                // 要出栈的元素
                int top = maxVals.pop();
                // 如果栈为空,或者当前值小于栈顶元素(入栈不会破坏单调递减的约定)
                // 则将刚刚出栈的元素,与新来的值 value 生成子树
                // 并计算这个子树的根结点结果
                if (maxVals.isEmpty() || maxVals.peek() > value) {
                    result += (top * value);
                } else {
                    // 如果栈不为空,并且栈顶元素小于新来的 value,说明 value 不能简单入栈
                    // 需要挪到期望的位置,所以这个 value 目前不参与子树的构建
                    // 而是将刚刚出栈的,和目前的 top (将要出栈的)结合,生成子树
                    result += (maxVals.peek() * top);
                }
            }
            maxVals.push(value);
        }

        // 还存在未生成树的叶子结点或者子树的根结点(记录的是子树的最大值)
        // 则需要将剩下的出栈,并生成最终的树
        while (maxVals.size() >= 2) {
            int top = maxVals.pop();
            result += top * maxVals.peek();
        }
        return result;
    }
}

总结

做题时应当多涂涂画画,研究题目究竟存在什么样的规律。比如 [6, 2, 3, 1] 这种情况,为什么 2 会选择与 3 结合,而不是让 3 与 1 结合。换而言之,我们读数组 [ 6, 2, 3] 时,是否能确定 2 与 3 能生成子树,并且不会因为后面出现的值而影响到。这些问题应当逐个梳理,归纳一些与本题息息相关的结论,然后再去梳理主要流程,编码实现等。

Smileyan
2024.06.24 00:09

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1857156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器视觉光源丨CCS高显色LED光源方案实现精准色彩成像

机器视觉系统中&#xff0c;光源设计作为图像成像效果的关键&#xff0c;今天我们一起来看看自然光领域的光源方案&#xff0c;以CCS光源来说&#xff0c;高显色(自然光)LED光源&#xff0c;使用了实现接近基准光源(太阳光和白炽灯)的自然发光颜色。它主要用于辨色检测、测色检…

如何免费的去使用connectedpapers?

免费使用connectedpapers 1. 打开谷歌浏览器2. 按住ctrlshiftN,进入无痕模式3. 不需要登录&#xff08;也就是访客模式&#xff09;4. 两次用完&#xff0c;关闭无痕模式&#xff08;继续重复步骤 2 - 4&#xff09; 1. 打开谷歌浏览器 2. 按住ctrlshiftN,进入无痕模式 输入网…

软件工程全套学习培训资料,实际优质项目编制及各类建设方案,信息安全,运维资料

目的&#xff1a;规范系统开发流程&#xff0c;提高系统开发效率。 立项申请需求分析方案设计方案评审开发调整测试阶段系统培训试运行测试验收投入使用 所有文档过去进主页获取。 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单列表部分文档清单&#xff1a;工作…

centOS7网络配置_NAT模式设置

第一步&#xff1a;查看电脑网卡 nat模式对应本地网卡的VMnet 8 &#xff0c;查看对应的IP地址。 第二步&#xff1a;虚拟网络编辑器 打开VMWare&#xff0c;编辑--虚拟网络编辑器&#xff0c;整个都默认设置好了&#xff0c;只需要查看对应的DHCP设置中对应的IP的起始&#…

MySQL数据库(五):事务

MySQL数据库中的事务是一种用来保证一系列操作要么全部成功&#xff0c;要么全部取消的机制。想象一下你去超市购物&#xff0c;拿了很多商品&#xff0c;如果中途发现没带钱包&#xff0c;你可以放弃这次购买&#xff0c;所有商品会回到原位。通过事务&#xff0c;可以确保数据…

智能化改造助力企业高质量发展

引言 背景介绍 在当今全球经济环境中&#xff0c;变化和不确定性已成为常态。企业面临的竞争压力不断增加&#xff0c;市场竞争日益激烈。新兴市场的崛起、技术进步和消费者需求的快速变化&#xff0c;使得传统的商业模式和生产方式面临巨大挑战。为了在这样的环境中保持竞争力…

yarn:终极包管理器指南 - 提高您的项目效率和性能

Yarn使用教程大纲 一、介绍1.1 什么是Yarn1.2 Yarn的优势1.3 Yarn与npm的比较 二、安装Yarn2.1 Windows安装Yarn2.2 macOS安装Yarn2.3 Linux安装Yarn2.4 注意事项 三、初始化项目3.1 在项目中使用Yarn3.2 创建新项目3.3 在已有项目中使用Yarn 四、添加依赖4.1 添加依赖4.1.1 安…

【STM32+FPGA】先进算力+强安全+边缘AI,64位STM32MP2聚焦工业4.0应用

工业应用数字化和智能化程度&#xff0c;是衡量新质生产力的重要标准。STM32最新一代64位微处理器STM32MP2凭借先进算力、丰富接口和高安全性&#xff0c;为高性能和高度互联的工业4.0应用赋能。 STM32MP2四大关键特性&#xff0c;为工业4.0应用赋能 STM32MP2系列的第一颗产品S…

windows密码设置(windows10)

1. 在网络列表中点击“网络和Internet设置",显示页面如下&#xff1a; 2. 点击“主页”,显示页面如下&#xff1a; 3. 点击“账户”,显示页面如下&#xff1a; 3. 点击“账户”,显示页面如下&#xff1a; 在上图中选择“登录选项”&#xff0c;再选中“密码”&#xff0c…

“论SOA在企业集成架构设计中的应用”写作框架,系统架构设计师

论文真题 企业应用集成(Enterprise Application Integration, EAI)是每个企业都必须要面对的实际问题。面向服务的企业应用集成是一种基于面向服务体系结构(Service-OrientedArchitecture,SOA&#xff09;的新型企业应用集成技术&#xff0c;强调将企业和组织内部的资源和业务…

无线领夹麦克风怎么挑选,揭秘能让声音变好听的领夹麦!

在这个数字媒体迅速扩张的时代&#xff0c;直播带货和Vlog视频制作已经成为内容创作的新宠。自媒体视频内容的兴起&#xff0c;不仅改变了人们获取信息的方式&#xff0c;也推动了相关音频设备市场的繁荣。无线领夹麦克风&#xff0c;凭借其便于携带和使用方便的特点&#xff0…

讨论顺序表

讨论顺序表 C中的vector模拟实现成员变量尾插数据push_back扩容reserve 构造函数和析构函数拷贝构造函数指定位置插入数据指定位置删除数据迭代器失效完整代码 C中&#xff0c;vector是可以改变大小的数组的序列容器。可以看做底层就是一个数组&#xff0c;容量满时扩容。 C中的…

nacos的创建

nacos压缩包 链接&#xff1a;https://pan.baidu.com/s/1AYVKZvosDkcMMbTIB48Iew?pwd1234 提取码&#xff1a;1234 首先将下载好的nacos压缩包上传到linux环境中&#xff0c;然后解压缩&#xff08;解压缩命令&#xff09; tar -zxvf nacos-server-2.3.0.tar.gz解压成功后如…

【五】【QT开发应用】C++中lambda表达式,值捕获,引用捕获,隐式捕获,lambda表达式的返回类型

Lambda表达式 复盘 Lambda表达式 Lambda 表达式是 C11 引入的一种特性&#xff0c;用于定义匿名函数。它使得可以在代码中方便地定义和使用小段函数&#xff0c;而无需专门定义一个命名的函数。这在需要传递函数作为参数或者需要定义内联函数时非常有用。 基本语法 基本语法…

Python-矩阵元素定位

[题目描述] 小理得到了一个 n 行 m 列的矩阵&#xff0c;现在他想知道第 x 行第 y 列的值是多少&#xff0c;请你帮助他完成这个任务。输入格式&#xff1a; 第一行包含两个数 n 和m &#xff0c;表示这个矩阵包含 n行 m 列。从第 2 行到第 n1 行&#xff0c;每行输入 m 个整数…

告别繁琐代码,迈向编程新境界—Java集合与泛型全面解析

在Java编程的征途中&#xff0c;集合&#xff08;Collection&#xff09;与泛型&#xff08;Generics&#xff09;是两大里程碑式的特性&#xff0c;它们不仅极大地提升了代码的灵活性和安全性&#xff0c;还帮助开发者简化了数据结构的处理逻辑&#xff0c;让编程之旅变得更加…

Appium APP测试学习

1、安装client编程库(客户端) (1)如果遇到以下问题可以使用全路径安装 (2)安装后导致selenium升级&#xff0c;导致某些方法失效&#xff1a;如find_element_by_id。解决方法&#xff1a;卸载两个安装包&#xff0c;后面重新安装 2、安装appium Server:&#xff08;服务端&…

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中&#xff0c;少不了使用word&#xff0c;这个是大家必备的软件&#xff0c;今天给大家分享word设置上标快捷键&#xff0c;希望在办公中能帮到您&#xff01; 1、添加上标 在录入一些公式&#xff0c;或者是化学产品时&#xff0c;需要添加上标内容…

笔记:记录状态并判重的方法

题目&#xff08;八数码问题&#xff09; 编号为1-8的8个正方形滑块被摆成3行3列&#xff08;有一个格子留空&#xff09;&#xff0c;如下图所示 81573642 每次可以把与空格相邻的滑块&#xff08;有公共边才算相邻&#xff09;一道空格中&#xff0c;而它原来的位置就成为…

Vue3.3 的 defineOptions 的使用,方便在 setup 语法糖中为组件命名和控制父子属性透传,包含在线运行实例欧

defineOptions 是 Vue3.3 的新的宏&#xff0c;可以通过 defineOptions 宏在 <script setup> 中使用选项式 API&#xff0c;也就是说可以在一个宏函数中设置 name, props, emits, render, 控制是否允许父子非 props 的属性透传等功能。 defineOptions 可以直接在 setup …