二叉树供暖题解| 豆包MarsCode AI刷题

54 阅读8分钟

题目解析

天气越来越冷了,村子里有留守老人缺少照顾,会在这个冬天里挨冻,小R想运用自己的知识帮帮他们。已知村庄里每户人家作为一个节点,这些节点可以组成一个二叉树。我们可以在二叉树的节点上供应暖炉,每个暖炉可以为该节点的父节点、自身及其子节点带来温暖。给定一棵二叉树,求使整个村庄都暖和起来至少需要供应多少个暖炉? 本题按层遍历顺序描述二叉树的节点情况。值为 1,代表存在一个节点,值为 0,代表不存在该节点。  

思维详解:

给定的输入以层序遍历的方式描述二叉树的节点分布,其中值为 1 表示节点存在,值为 0 表示节点不存在。目标是根据这一结构判断需要安装暖炉的最少数量,以最优化的方式满足覆盖所有节点的要求。本问题本质上是一个二叉树节点覆盖的优化问题,需要基于节点的状态进行合理推导和规划。

代码详解:

直接对数组进行操作的话会十分麻烦,所以这里将数组转换为二叉树进行进一步解题。如果数组本身长度为0,那树根本就不存在,直接返回空树即可。如果数组长度不为0,那么就可以认真来构建二叉树了。层序遍历数组的第一个元素就是树的根节点,所以我们可以先把根节点创建出来。接着,因为对二叉树进行非递归的层序遍历时,会用到一个队列来进行辅助遍历,这里还原二叉树时,也要用一个队列来辅助构建,与遍历时差不多,只不过一个是取,一个是放。将根节点放入队列中,再定义一个指针来记录我们构建到数组中的哪个元素之后,就可以开始层序构建了。从队列中取出一个节点,构造它的左右节点。在保证数组没有越界的情况下,取出指针所指向的元素,如果为1说明有节点,进行构造;0说明没有节点,跳过。并将构造好的节点继续存入队列中,同时移动数组指针。

构造完二叉树之后,我们就可以开始计算暖炉的个数了。我们要用贪心的思想来解决,因为暖炉可以覆盖上下三层,如果我们在叶子节点放置暖炉的话,就会浪费掉一层,从贪心的角度是不可能这么做的,所以我们只能在非叶子节点放置暖炉。 既然不能在叶子节点放置暖炉,那我们就得确定某个节点是否为叶子结点,也就是要判断该节点的两个子节点是否为空,再来做其他考虑,所以我们用到的树的遍历方式是后序遍历。

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

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

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

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

接着,我们就可以开始递归后序遍历了。递归的结束条件是遇到空节点返回供暖的状态(1)。接着递归左子节点和右子节点,分别或者左右两个子节点的状态,根据左右两个子节点的状态,判断当前节点的状态。如果左右节点都被供暖了,那么当前节点就未被供暖,需要父节点安装暖炉来供暖当前节点,返回未供暖的状态(2)。如果左右节点其中有一个节点未被供暖,那么当前节点就必须安装暖炉来供暖子节点,返回安装暖炉的状态(0)。如果左右节点其中有一个节点安装暖炉,那么当前节点已被供暖,返回供暖的状态(1)。 后序遍历结束之后,我们可能会出现根节点刚好未被供暖的情况,所以我们得多加一步判断,如果根节点未被供暖,我们还要多加一个暖炉。最后,将暖炉的数量返回即可。

 

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;
         //根据层序遍历的数组构造二叉树
         Node root = buildTree(nodes);
         //判断根节点是否已经供暖
         if(postorder_traversal(root) == 2){
             result++;
         }
         return result;
     }
     //后序遍历
     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 here
        int[] 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);
    }
}

 

知识总结

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

 

学习计划

豆包MarsCode AI在我的刷题过程中给了我很大的帮助,千里之行始于足下,解题第一步就是读懂题,AI助手很好的为我明晰了题目要义。并且在解题过程中,AI助手也可以为我实时分析代码逻辑与漏洞,以防编程过程中的逻辑模糊现象,大大提升了解题效率。

 

工具运用

这里建议其他码友在题意不明时询问豆包AI,让其进行读题,明确题意。代码编程卡顿时借助AI对现有代码进行逻辑分析,获取接下来的解题思路,可有效提升效率,但要注意AI不是万能的,不能全部照抄,要融入自己的理解,才能获得最精确的题解。同时,如果发现代码于某些样例存在问题,可询问豆包AI当前代码是否存在边界问题没有考虑到。