题目解析
天气越来越冷了,村子里有留守老人缺少照顾,会在这个冬天里挨冻,小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当前代码是否存在边界问题没有考虑到。