题目内容
在寒冷的冬天,为了帮助村庄中的留守老人,我们需要在村庄的二叉树结构上放置暖炉。每个暖炉可以为一个节点及其父节点和子节点提供温暖。给定一个描述二叉树层序遍历的数组,其中1代表存在节点,0代表不存在节点,求出至少需要放置多少个暖炉,以确保整个村庄的温暖。
题目要求
编写一个函数 solution
,接收一个整数数组 nodes
作为输入,返回一个整数,表示至少需要放置的暖炉数量。
注:题目没有为我们传入一个树结构,我们需要自行编写数组转树的函数
思路解析
从局部出发。假设当前节点为 root,其左右孩子为 left,right。如果要覆盖以 root 为根的树,有两种情况:
- 若在 root 处安放暖炉,则孩子 left,right 一定也会被覆盖到。此时,只需要保证 left 的两棵子树被覆盖,同时保证 right 的两棵子树也被覆盖即可。
- 否则, 如果 root 处不安放暖炉,则除了覆盖 root 的两棵子树之外,孩子 left,right 之一必须要安装暖炉,从而保证 root 会被覆盖到。
因此,这个问题可以转化为一个树形动态规划问题。我们需要为每个节点考虑三种状态:
- 节点安装了暖炉。
- 节点的子节点安装了暖炉,因此该节点被覆盖。
- 节点既没有安装暖炉,也没有被覆盖。
对于每个节点,我们需要决定它的状态,并递归地决定其子节点的状态,以最小化暖炉的总数量。
核心的递归函数 dfs
返回一个长度为3的数组,其中:
array[0]
表示当前节点安装暖炉时的最小暖炉数量。array[1]
表示以当前节点为根的子树被覆盖时的最小暖炉数量,不一定在当前节点安装。array[2]
表示当前节点被覆盖但未安装暖炉时的最小暖炉数量。
核心知识
- 二叉树的遍历与动态规划。
- 树形DP的状态定义与状态转移。
二叉树的遍历 是指按照一定的顺序访问二叉树中的所有节点。常见的遍历方式有:
- 前序遍历:先访问根节点,然后递归遍历左子树,最后递归遍历右子树。
- 中序遍历:先递归遍历左子树,然后访问根节点,最后递归遍历右子树。
- 后序遍历:先递归遍历左子树,然后递归遍历右子树,最后访问根节点。
- 层序遍历:按照树的层级,从上到下,从左到右依次访问每个节点。
在解决本题时,我们采用层序遍历的方式读取nodes数组;同时,我们使用的是深度优先搜索(DFS),它是一种后序遍历的方式,因为我们需要先知道子节点的状态,才能决定父节点的状态。
动态规划(DP)通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划通常用于求解最优化问题。
在本题中,我们使用树形DP来解决问题,树形DP是动态规划在树结构上的应用。
完整代码
import java.util.LinkedList;
import java.util.Queue;
public class Main {
// 深度优先搜索函数,用于计算每个节点在不同状态下的最小暖炉数量
public static int[] dfs(TreeNode root) {
// 如果当前节点为空或者节点不存在(值为0),则返回一个极大值和一个覆盖状态为0的数组
if (root == null || root.val == 0) {
// 返回的数组表示三种状态:[安装暖炉, 不安装但被覆盖, 不安装且不被覆盖]
// 由于节点不存在,所以安装暖炉的数量设置为极大值,其他两种状态设置为0
return new int[]{Integer.MAX_VALUE / 2, 0, 0};
}
// 递归计算左子树和右子树的状态数组
int[] leftArray = dfs(root.left);
int[] rightArray = dfs(root.right);
// 初始化当前节点的状态数组
int[] array = new int[3];
// 状态0: 当前节点安装暖炉,则左子树和右子树都必须被覆盖,但不一定安装暖炉
array[0] = leftArray[2] + rightArray[2] + 1;
// 状态1: 当前节点不安装暖炉,但被覆盖,需要考虑以下两种情况的最小值:
// 1. 当前节点安装暖炉
// 2. 左子树安装暖炉,右子树被覆盖;或者右子树安装暖炉,左子树被覆盖
array[1] = Math.min(array[0], Math.min(leftArray[0] + rightArray[1], rightArray[0] + leftArray[1]));
// 状态2: 当前节点不安装暖炉,也不被覆盖,左子树和右子树必须被覆盖,但不安装暖炉
array[2] = Math.min(array[0], leftArray[1] + rightArray[1]);
// 返回当前节点的状态数组
return array;
}
// 主函数,计算最少需要的暖炉数量
public static int solution(int[] nodes) {
// 根据输入数组构建二叉树
TreeNode root = buildTree(nodes);
// 执行深度优先搜索,获取根节点的状态数组
int[] array = dfs(root);
// 返回状态1的值,即不安装暖炉但被覆盖的最小暖炉数量
return array[1];
}
// 二叉树节点类
static class TreeNode {
int val; // 节点值,1表示节点存在,0表示节点不存在
TreeNode left; // 左子节点
TreeNode right; // 右子节点
// 构造函数,初始化节点值
TreeNode(int val) {
this.val = val;
}
}
// 根据层序遍历数组构建二叉树
public static TreeNode buildTree(int[] nodes) {
// 如果输入数组为空或长度为0,则返回null
if (nodes == null || nodes.length == 0) {
return null;
}
// 创建根节点,根节点总是存在的
TreeNode root = new TreeNode(1);
// 使用队列辅助构建二叉树
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
// 从数组的第二个元素开始遍历
int index = 1;
while (index < nodes.length) {
TreeNode current = queue.poll(); // 获取当前处理的节点
// 处理左子节点
if (nodes[index] == 1) {
current.left = new TreeNode(1); // 创建左子节点
queue.offer(current.left); // 将左子节点加入队列
}
index++; // 移动到下一个元素
// 处理右子节点
if (index < nodes.length && nodes[index] == 1) {
current.right = new TreeNode(1); // 创建右子节点
queue.offer(current.right); // 将右子节点加入队列
}
index++; // 移动到下一个元素
}
// 返回构建完成的二叉树的根节点
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, 1, 1, 1, 1};
System.out.println(solution(nodes1) );
System.out.println(solution(nodes2) );
System.out.println(solution(nodes3) );
}
使用豆包marscode AI的优势
在解决这类问题时,豆包marscode AI可以提供以下优势:
- 代码生成:AI能够根据问题描述快速生成相应的代码框架,节省编写代码的时间。
- 算法优化:AI能够分析代码并提供可能的优化建议,帮助提高算法的效率。
- 错误检测:AI能够检测代码中的错误,并提供修改建议,确保代码的正确性。
- 学习辅助:AI能够解释代码中的复杂概念,帮助理解树形DP等高级算法。