MarsCode算法题之二叉树供暖问题

1.问题描述

        天气越来越冷了,村子里有留守老人缺少照顾,会在这个冬天里挨冻,小华想运用自己的知识帮帮他们。已知村庄里每户人家作为一个节点,这些节点可以组成一个二叉树。我们可以在二叉树的节点上供应暖炉,每个暖炉可以为该节点的父节点、自身及其子节点带来温暖。给定一棵二叉树,求使个村庄都暖和起来至少需要供应多少个暖炉?

        注意

        输入为一个数组,按层遍历顺序描述二叉树的节点情况。值为 1,代表存在一个节点,值为 0,代表不存在该节点。

        示例1

输出最少暖炉供应数量。

输入样例

1, 1, 0, 1, 1

输出样例

1

        提示

        树的节点数的范围是 [1, 1000]。

        难度等级

                中等

        题目链接

        二叉树供暖问题

2.解题思路

        这道二叉树供暖的问题,和LeetCode的监控二叉树问题很像,或者说核心逻辑一模一样,大家有兴趣的话,也可以先去看一下LeetCode的监控二叉树的那道题,我的专栏里也有。我就不贴链接了,哈哈哈哈哈。

        说是二叉树问题,但题目一开始给我们的是一个存储层序遍历的数组,1代表节点存储,0代表节点不存在。直接对数组进行操作的话,说实话,不好操作,很麻烦,所以我选择了将数组转化成二叉树来做题,毕竟题目也说了这是个二叉树问题。

        首先,定义一个基本的节点,包含左右节点和一个构造方法。

//树节点
class Node{int data;Node left;Node right;public Node(int data){this.data = data;left = null;right = null;}
}

        接着,我们要来用一个方法解决如何将层序遍历数组转化为二叉树的问题。

        如果数组本身长度为0,那树根本就不存在,直接返回空树即可。如果数组长度不为0,那么就可以认真来构建二叉树了。层序遍历数组的第一个元素就是树的根节点,所以我们可以先把根节点创建出来。

         if (nodes == null || nodes.length == 0) {return null;}//构建根节点Node root = new Node(nodes[0]);

        接着,不知道大家记不记得,对二叉树进行非递归的层序遍历时,我们会用到一个队列来进行辅助遍历,这里我们要来还原二叉树时,也要用一个队列来辅助我们构建,与遍历时差不多,只不过一个是取,一个是放罢了。

        我们将根节点放入队列中,再定义一个指针来记录我们构建到数组中的哪个元素之后,就可以开始层序构建了。

         //创建一个队列用于存储节点Queue<Node> queue = new LinkedList<>();queue.offer(root);//从数组的第二个元素开始int i = 1;

        从队列中取出一个节点,构造它的左右节点。在保证数组没有越界的情况下,取出指针所指向的元素,如果为1说明有节点,进行构造;0说明没有节点,跳过。并将构造好的节点继续存入队列中,同时移动数组指针。

             //从队列中取出一个节点Node current = queue.poll();//处理左子节点if(i < nodes.length && nodes[i] == 1){current.left = new Node(nodes[i]);queue.offer(current.left);}//移动指针i++;//处理右子节点if(i < nodes.length && nodes[i] == 1){current.right = new Node(nodes[i]);queue.offer(current.right);}//移动指针i++;

        层序构造完成后,将二叉树的根节点返回。

         return root;

        构造完二叉树之后,我们就可以开始计算暖炉的个数了。我们要用贪心的思想来解决,因为暖炉可以覆盖上下三层,如果我们在叶子节点放置暖炉的话,就会浪费掉一层,从贪心的角度是不可能这么做的,所以我们只能在非叶子节点放置暖炉

//根据层序遍历的数组构造二叉树Node root = buildTree(nodes);

        既然不能在叶子节点放置暖炉,那我们就得确定某个节点是否为叶子结点,也就是要判断该节点的两个子节点是否为空,再来做其他考虑,所以我们用到的树的遍历方式是后序遍历

        接着,我们来分析一下,每一个节点都有可能是什么状态。首先,节点可能是安装了暖炉的状态,我们标记0;其次,节点还可能是供暖的状态(没有暖炉),我们标记为1;最后,节点还可能是没有被供暖的状态,我们标记为2。顺便定义一个变量来存储所需暖炉的数量。

     //后序遍历//每个节点有三种情况//0 -> 安装暖炉  1 -> 已供暖  2 -> 未供暖//记录暖炉个数static int result  = 0;

        我们已经清楚了,叶子节点不能安装暖炉,也就是叶子节点不可能为0,每一个叶子节点都得被供暖,所以叶子节点的状态肯定是供暖的状态(1)。接着,我们要来分析一下,空节点要标记为什么状态。

        假设空节点标记为安装暖炉的状态(0),那对于只有一个叶子结点的父节点来说,它是被供暖的,那么暖炉会被安装到叶子结点的爷爷节点那里,这样实际上,叶子节点是没有被供暖到的,所以这种情况排除。

        假设空节点标记为未供暖的状态(2),那么叶子节点有两个空节点,那按照每个节点都要供暖的逻辑,我们会在叶子结点安装暖炉,这不符合我们一开始的贪心思想。

        假设空节点标记为供暖的状态(1),那么按照每个节点都要供暖的逻辑,我们可以不用管这个节点了,因为它已经供暖了。因此,我们得出来空节点标记为1的结论。

        接着,我们就可以开始递归后序遍历了。递归的结束条件是遇到空节点返回供暖的状态(1)。接着递归左子节点和右子节点,分别或者左右两个子节点的状态,根据左右两个子节点的状态,判断当前节点的状态。

         //空节点的情况为被供暖if(root == null){return 1;}//左int left = postorder_traversal(root.left);//右int right = postorder_traversal(root.right);

        如果左右节点都被供暖了,那么当前节点就未被供暖,需要父节点安装暖炉来供暖当前节点,返回未供暖的状态(2)。

         //如果两个叶子节点都已供暖if(left == 1 && right == 1){//返回当前节点未供暖return 2;}

        如果左右节点其中有一个节点未被供暖,那么当前节点就必须安装暖炉来供暖子节点,返回安装暖炉的状态(0)。

         //如果两个叶子节点有一个未供暖if(left == 2 || right == 2){//安装暖炉给叶子节点供暖result++;return 0;}

        如果左右节点其中有一个节点安装暖炉,那么当前节点已被供暖,返回供暖的状态(1)。

         //如果两个叶子节点其中一个安装了暖炉if(left == 0 || right == 0){return 1;}

        后序遍历结束之后,我们可能会出现根节点刚好未被供暖的情况,所以我们得多加一步判断,如果根节点未被供暖,我们还要多加一个暖炉。

      //判断根节点是否已经供暖if(postorder_traversal(root) == 2){result++;}

        最后,将暖炉的数量返回即可。

         return result;

3.代码展示

import java.util.LinkedList;
import java.util.Queue;
//树节点
class Node{int data;Node left;Node right;public Node(int data){this.data = data;left = null;right = null;}
}
public class Main {//记录暖炉个数static int result  = 0;public static int solution(int[] nodes) {// Please write your code here//因为result是静态变量,所以每次调用该方法时都初始化为0比较好//防止沿用之前的暖炉个数result = 0;//根据层序遍历的数组构造二叉树Node root = buildTree(nodes);//判断根节点是否已经供暖if(postorder_traversal(root) == 2){result++;}return result;}//后序遍历//每个节点有三种情况//0 -> 安装暖炉  1 -> 已供暖  2 -> 未供暖public static int postorder_traversal(Node root){//空节点的情况为被供暖if(root == null){return 1;}//左int left = postorder_traversal(root.left);//右int right = postorder_traversal(root.right);//根//如果两个叶子节点都已供暖if(left == 1 && right == 1){//返回当前节点未供暖return 2;}//如果两个叶子节点有一个未供暖if(left == 2 || right == 2){//安装暖炉给叶子节点供暖result++;return 0;}//如果两个叶子节点其中一个安装了暖炉if(left == 0 || right == 0){return 1;}//实际上不会走到这一步,上面已经将所有情况都考虑了return 1;}//根据层序遍历的数组构建二叉树public static Node buildTree(int[] nodes) {if (nodes == null || nodes.length == 0) {return null;}//构建根节点Node root = new Node(nodes[0]);//创建一个队列用于存储节点Queue<Node> queue = new LinkedList<>();queue.offer(root);//从数组的第二个元素开始int i = 1;while(!queue.isEmpty() && i < nodes.length){//从队列中取出一个节点Node current = queue.poll();//处理左子节点if(i < nodes.length && nodes[i] == 1){current.left = new Node(nodes[i]);queue.offer(current.left);}//移动指针i++;//处理右子节点if(i < nodes.length && nodes[i] == 1){current.right = new Node(nodes[i]);queue.offer(current.right);}//移动指针i++;}return root;}public static void main(String[] args) {//  You can add more test cases hereint[] nodes1 = {1, 1, 0, 1, 1};int[] nodes2 = {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1};int[] nodes3 = {1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1};System.out.println(solution(nodes1) == 1);System.out.println(solution(nodes2) == 3);System.out.println(solution(nodes3) == 3);}
}

4.总结

        这道题的关键是将层序遍历数组转化为一颗二叉树,接着用贪心的思想进行分析遍历。每个节点的状态有三种情况,依次是安装暖炉、被供暖和未被供暖。叶子节点不可能安装暖炉,同时空节点默认为已经被供暖,仔细分析这三种情况,并对节点做出相应的处理即可解决这道题了。好了,这道题啰嗦得有点多了,我就不bb了。祝大家刷题愉快~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面试要点02 - 自动装箱与拆箱的原理与性能解析

本文目录 一、引言二、自动装箱与拆箱的底层原理2.1 编译器的处理机制2.2 字节码层面的分析2.3 缓存机制的实现 三、性能影响的深度分析3.1 内存开销分析3.2 CPU开销分析 四、实际应用中的常见陷阱4.1 空指针异常陷阱4.2 包装类型的比较陷阱 五、最佳实践与优化建议5.1 性能优化…

速通LoRA:《LoRA: Low-Rank Adaptation of Large Language Models》全文解读

文章目录 总览AbstractIntroductionProblem StatementAren’t Existing Solutions Good Enough?Our MethodLow-Rank-Parametrized Update MatricesApplying LoRA to Transformer 何为高斯随机初始化Empirical ExperimentsBaselinesRoBERTa base/largeDeBERTa XXLGPT-2 medium/…

【Java学习】电脑基础操作和编程环境配置

CMD 在Windows中用命令行的方式操作计算机。 打开CMD Win R输入CMD按下回车键 Win E 进入我的电脑 常用的CMD命令 盘符名称冒号 说明&#xff1a;盘符切换 举例&#xff1a;E:回车&#xff0c;表示切换到E盘 dir 说明&#xff1a;查看当前路径下的内容 cd目录 说明&a…

索引【MySQL】

文章目录 聚簇索引 VS 非聚簇索引索引MySQL与磁盘交互的基本单位主键索引索引操作唯一索引的创建普通索引的创建复合索引 索引创建原则 聚簇索引 VS 非聚簇索引 MyISAM存储引擎 - 主键索引结构 MyISAM存储引擎同样采用B树作为索引的基本数据结构 与InnoDB存储引擎的B树不同的…

c语言数据结构与算法--简单实现队列的入队和出队

&#xff08;一&#xff09;队列的基本概念 和栈相反&#xff0c;队列(Queue)是一种先进先出&#xff08;First In First Out&#xff09;的线性表。只 允许在表的一端进行插入&#xff0c;而在另一端删除元素&#xff0c;如日常生活中的排队现象。队列中 允许插入的一端叫队尾…

【缓存策略】你知道 Cache Aside(缓存旁路)这个缓存策略吗

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

2.操作系统常见面试问题2

2.19 说说什么是堆栈溢出&#xff0c;会怎么样&#xff1f; 堆溢出&#xff08;Heap Overflow&#xff09;是指程序在运行时向堆内存区域写入了超出预定大小的数据&#xff0c;导致堆内存区域的数据结构&#xff08;如动态分配的内存块&#xff09;被破坏&#xff0c;从而引发…

基于TI AM62A+FPGA实现FPDLINK III车载摄像头解决方案

功能概述 本模块主要包含FPDLINKIII/CML收发信号与HDMI/SDI/USB信号、千兆网络信号&#xff0c;支持客户按照按照指定功能定制 当前默认功能为FPD LINK III/CML转为HDMI/SDI/UVC信号 性能参数 名称 描述 供电接口 DC12V FPD LINK RX GM8914 FPD LINK TX GM8913 千兆网…

丹摩征文活动 | SD3+ComfyUI的图像部署实践

一、前言 作为Stability AI 推出的一款革命性的文本转图像开源模型&#xff0c;Stable Diffusion 3&#xff08;简称SD3&#xff09;在图像质量、文本内容生成、理解复杂指令以及资源利用效率方面&#xff0c;都有着不俗的表现。 SD3的Medium版本&#xff0c;拥有20亿参数&am…

介绍几个提取视频文案的Coze插件

用过coze的朋友应该都知道“链接读取”这个插件&#xff0c;它不但可以读取网页内容&#xff0c;而且还可以提取视频链接的内容&#xff0c;但是对于有些平台的网址&#xff0c;它就有些无能为力了。 这里就可以用到我们今天的主角之一“字幕获取”插件。 一、字幕获取插件 从…

MIT 6.S081 Lab1: Xv6 and Unix utilities翻译

Lab1: Xv6 and Unix utilities 文章目录 Lab1: Xv6 and Unix utilities实验任务启动xv6(难度&#xff1a;Easy)sleep(难度&#xff1a;Easy)pingpong&#xff08;难度&#xff1a;Easy&#xff09;Primes(素数&#xff0c;难度&#xff1a;Moderate/Hard)find&#xff08;难度&…

YOLOv11融合ICCV[2023]动态蛇形卷积Dynamic模块及相关改进思路|YOLO改进最简教程

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《Dynamic Snake Convolution based on Topological Geometric Constraints for Tubular Structure Segmentation》 一、 模块介绍 论文链接&#xff…

Sam Altman:年底将有重磅更新,但不是GPT-5!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

信息网络安全——AES加密算法

算法背景介绍 该算法是由美国发明的&#xff0c;1997年NIST发布算法征集公告&#xff0c;98年入围15个候选算法&#xff0c;99年进入五强&#xff0c;00年凭借安全性&#xff0c;性能&#xff0c;大小实现特性为标准最终选定&#xff0c;01年正式发布AES标准。   选择AES主要…

arm 汇编技巧

汇编标号&#xff1a;f表示forward&#xff0c; b表示backward&#xff1a; Here is an example: 1: branch 1f 2: branch 1b 1: branch 2f 2: branch 1b Which is the equivalent of: label_1: branch label_3 label_2: branch label_1 label_3: branch label_4 label_4: bra…

初始JavaEE篇 —— 文件操作与IO

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 文件介绍 Java标准库中提供操作文件的类 文件系统操作 File类的介绍 File类的使用 文件内容操作 二进制文件的读写操作…

华为网络设备这些“危险命令”,切记不能瞎操作!

在华为网络设备上&#xff0c;有一些“危险操作”命令&#xff0c;因为它们可能会对设备的正常运行、配置数据或网络安全产生重大影响。 在使用这些命令时&#xff0c;需要非常谨慎&#xff0c;确保理解其作用并备份当前配置。 删除配置或数据的命令 reset saved-configurat…

Linux中线程的基本概念与线程控制

Linux操作系统中线程 1、进程指的是加载进内存的程序&#xff0c;进程 内核数据结构 进程代码和数据 2、进程在执行ABCD四个函数时是一个单执行流&#xff0c;而如果想让AB函数和CD函数并发执行&#xff0c;我们通常会创建一个子进程&#xff0c;但这意味着需要创建新的进程…

Jenkins声明式Pipeline流水线语法示例

系列文章目录 docker搭建Jenkins2.346.3版本及常用工具集成配置(ldap、maven、ansible、npm等) docker安装低版本的jenkins-2.346.3,在线安装对应版本插件失败的解决方法 文章目录 系列文章目录jenkins流水线基础1、pipeline1.1、什么是pipeline&#xff1f;1.2、为什么使用pi…

Leetcode 找出字符串中第一个匹配项的下标

算法思想&#xff1a; 检查特殊情况&#xff1a;首先判断needle是否为空字符串。如果是空字符串&#xff0c;根据题意直接返回0&#xff0c;因为空子串默认在任何字符串的起始位置。 获取字符串长度&#xff1a;定义m为haystack的长度&#xff0c;n为needle的长度&#xff0c;…