如何利用AI刷题基础篇2——高效刷题|豆包MarsCode AI刷题

66 阅读6分钟

本文专注于记录我如何使用AI完成算法刷题练习,以此来分享如何高效利用AI刷题。


#75二叉树供暖问题

问题描述

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

输入格式

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

输出格式

输出最少暖炉供应数量。

输入样例

1, 1, 0, 1, 1

输出样例

1

数据范围

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


思路

首先对于问题,我们依旧先明确输入,输出和处理。我们交给AI试试。


  • 用户:帮我将题目的输入,输出和我要做的处理明确

  • AI:好的,让我们明确一下题目的输入、输出以及你需要处理的内容。

  • 输入

    • 输入格式: 一个数组,按层遍历顺序描述二叉树的节点情况。值为 1 代表存在一个节点,值为 0 代表不存在该节点。
    • 示例输入{1, 1, 0, 1, 1}
  • 输出

    • 输出格式: 一个整数,表示使整个村庄都暖和起来至少需要供应的暖炉数量。
    • 示例输出1
  • 你需要做的处理

    1. 解析输入: 将输入的数组解析成一个二叉树结构。
    2. 计算最少暖炉数量: 设计一个算法,计算出使整个二叉树的所有节点都暖和起来所需的最少暖炉数量。

根据AI的回答,我们可以发现:1.我们要构造一个二叉树来存储输入的数组;2.我们要构造一个算法输出覆盖整棵树需要的最少暖炉数量。3.调用这些函数,解决任务。那么就开始动手吧。

代码构建

第一个任务:构造二叉树

完成任务一,定义二叉树节点,并用层序遍历构建二叉树。

// 定义二叉树节点
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int v) : val(v), left(nullptr), right(nullptr) {}
};
  • 定义了二叉树的节点结构,包括节点值 val,左子节点指针 left 和右子节点指针 right

  • 构造函数 TreeNode(int v) 初始化节点值为 v,左、右指针为空。

// 根据层序遍历构建二叉树
TreeNode* build_tree(const vector<int>& nodes) {
    if (nodes.empty() || nodes[0] == 0) return nullptr;

    TreeNode* root = new TreeNode(nodes[0]);
    queue<TreeNode*> q;
    q.push(root);
    int i = 1;

    while (i < nodes.size()) {
        TreeNode* current = q.front();
        q.pop();
        if (current) {
            if (i < nodes.size() && nodes[i] != 0) {
                current->left = new TreeNode(nodes[i]);
                q.push(current->left);
            }
            i++;
            if (i < nodes.size() && nodes[i] != 0) {
                current->right = new TreeNode(nodes[i]);
                q.push(current->right);
            }
            i++;
        }
    }
    return root;
}
  • 功能:根据层序遍历的数组构建二叉树。

  • 参数:一个整数向量 nodes,用 10 分别表示节点是否存在。

  • 实现

    • 创建根节点 root,并用队列 queue 辅助构造整棵树。
    • 遍历输入数组 nodes,为当前节点依次添加左右子节点(如果它们存在)。
  • 返回值:构建好的二叉树的根节点。

第二个任务:计算最少暖炉

// 计算需要的最少暖炉数量
int min_heaters(TreeNode* root) {
    int heaters = 0;

    function<int(TreeNode*)> dfs = [&](TreeNode* node) -> int {
        if (!node) return 1; // 空节点视为已覆盖

        int left = dfs(node->left);
        int right = dfs(node->right);

        if (left == 0 || right == 0) {
            // 如果子节点未被覆盖,需要放置暖炉
            heaters++;
            return 2; // 当前节点放置暖炉
        }
        if (left == 2 || right == 2) {
            // 如果子节点有暖炉,则当前节点被覆盖
            return 1;
        }
        return 0; // 当前节点未被覆盖
    };

    if (dfs(root) == 0) {
        // 如果根节点未被覆盖,则需要放置一个暖炉
        heaters++;
    }
    return heaters;
}
  • 功能:计算覆盖整棵树需要的最少暖炉数量。

  • 实现逻辑

    • 使用后序遍历的思想递归地遍历每个节点,维护当前节点和其子节点的状态。

    • 定义一个递归函数 dfs(TreeNode*),返回值表示当前节点的覆盖状态:

      • 0:当前节点未被覆盖。
      • 1:当前节点被覆盖,但没有暖炉。
      • 2:当前节点放置了暖炉。
    • 如果当前节点的左右子节点中有未覆盖的节点(0),当前节点必须放置暖炉,计数器 heaters 加一。

    • 如果左右子节点中有暖炉(2),当前节点被覆盖。

    • 如果左右子节点均被覆盖但没有暖炉,当前节点未被覆盖(0)。

    • 特殊情况:如果根节点最终未被覆盖,需要额外放置一个暖炉。

  • 返回值:所需暖炉的总数量。

第三个任务:调用函数解决问题

// 解决函数
int solution(const vector<int>& nodes) {
    TreeNode* root = build_tree(nodes);
    int result = min_heaters(root);

    // 释放动态分配的树
    function<void(TreeNode*)> free_tree = [&](TreeNode* node) {
        if (!node) return;
        free_tree(node->left);
        free_tree(node->right);
        delete node;
    };
    free_tree(root);

    return result;
}

总结

核心思想

  1. 后序遍历(Post-order Traversal)

    • 使用后序遍历从叶子节点开始递归地计算子树的覆盖状态,逐步向上解决问题。
    • 这种方式能够确保在处理一个节点时,其所有子节点的状态已经被确定。
  2. 动态规划的状态转移

    • 每个节点有三种状态:

      • 0:未被覆盖。
      • 1:被覆盖但未放置暖炉。
      • 2:放置了暖炉。
    • 当前节点的状态取决于其子节点的状态,状态转移的逻辑如下:

      • 如果任意一个子节点是 0(未被覆盖),当前节点必须放置暖炉(状态变为 2)。
      • 如果任意一个子节点是 2(子节点有暖炉),当前节点被覆盖(状态变为 1)。
      • 如果所有子节点是 1(被覆盖但无暖炉),当前节点未被覆盖(状态变为 0)。
  3. 贪心思想

    • 通过后序遍历的特性,优先保证从底层叶子节点到根节点的局部最优:

      • 只在必要时(子节点未被覆盖)放置暖炉。
      • 每次放置暖炉都覆盖最多的节点(当前节点及其子节点)。
    • 这种局部最优的选择能够确保最终解的全局最优。

  4. 特例处理

    • 如果递归遍历后,根节点未被覆盖(状态为 0),需要额外放置一个暖炉。这是算法的边界条件,确保根节点始终被覆盖。

本文通过动态规划与贪心相结合的思想,利用后序遍历高效解决了最少暖炉覆盖问题,主要特点包括:

  • 自底向上 的动态规划状态转移。
  • 在局部最优的基础上保证全局最优的贪心选择。
  • 时间和空间复杂度较低,适合处理大规模的二叉树问题。