刷题实践:二叉树供暖问题 | 豆包MarsCode AI刷题

194 阅读6分钟

题目内容

在寒冷的冬天,为了帮助村庄中的留守老人,我们需要在村庄的二叉树结构上放置暖炉。每个暖炉可以为一个节点及其父节点和子节点提供温暖。给定一个描述二叉树层序遍历的数组,其中1代表存在节点,0代表不存在节点,求出至少需要放置多少个暖炉,以确保整个村庄的温暖。

题目要求

编写一个函数 solution,接收一个整数数组 nodes 作为输入,返回一个整数,表示至少需要放置的暖炉数量。

注:题目没有为我们传入一个树结构,我们需要自行编写数组转树的函数

思路解析

从局部出发。假设当前节点为 root,其左右孩子为 left,right。如果要覆盖以 root 为根的树,有两种情况:

  • 若在 root 处安放暖炉,则孩子 left,right 一定也会被覆盖到。此时,只需要保证 left 的两棵子树被覆盖,同时保证 right 的两棵子树也被覆盖即可。
  • 否则, 如果 root 处不安放暖炉,则除了覆盖 root 的两棵子树之外,孩子 left,right 之一必须要安装暖炉,从而保证 root 会被覆盖到

因此,这个问题可以转化为一个树形动态规划问题。我们需要为每个节点考虑三种状态:

  1. 节点安装了暖炉。
  2. 节点的子节点安装了暖炉,因此该节点被覆盖。
  3. 节点既没有安装暖炉,也没有被覆盖。

对于每个节点,我们需要决定它的状态,并递归地决定其子节点的状态,以最小化暖炉的总数量。 核心的递归函数 dfs 返回一个长度为3的数组,其中:

  • array[0] 表示当前节点安装暖炉时的最小暖炉数量。
  • array[1] 表示以当前节点为根的子树被覆盖时的最小暖炉数量,不一定在当前节点安装。
  • array[2] 表示当前节点被覆盖但未安装暖炉时的最小暖炉数量。

核心知识

  • 二叉树的遍历与动态规划。
  • 树形DP的状态定义与状态转移。

二叉树的遍历 是指按照一定的顺序访问二叉树中的所有节点。常见的遍历方式有:

  • 前序遍历:先访问根节点,然后递归遍历左子树,最后递归遍历右子树。
  • 中序遍历:先递归遍历左子树,然后访问根节点,最后递归遍历右子树。
  • 后序遍历:先递归遍历左子树,然后递归遍历右子树,最后访问根节点。
  • 层序遍历:按照树的层级,从上到下,从左到右依次访问每个节点。

在解决本题时,我们采用层序遍历的方式读取nodes数组;同时,我们使用的是深度优先搜索(DFS),它是一种后序遍历的方式,因为我们需要先知道子节点的状态,才能决定父节点的状态。

QQ_1731910624307.png

动态规划(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能够分析代码并提供可能的优化建议,帮助提高算法的效率。

image.png

  • 错误检测:AI能够检测代码中的错误,并提供修改建议,确保代码的正确性。
  • 学习辅助:AI能够解释代码中的复杂概念,帮助理解树形DP等高级算法。

image.png