本文专注于记录我如何使用AI完成算法刷题练习,以此来分享如何高效利用AI刷题。
#75二叉树供暖问题
问题描述
天气越来越冷了,村子里有留守老人缺少照顾,会在这个冬天里挨冻,小华想运用自己的知识帮帮他们。已知村庄里每户人家作为一个节点,这些节点可以组成一个二叉树。我们可以在二叉树的节点上供应暖炉,每个暖炉可以为该节点的父节点、自身及其子节点带来温暖。给定一棵二叉树,求使个村庄都暖和起来至少需要供应多少个暖炉?
输入格式
输入为一个数组,按层遍历顺序描述二叉树的节点情况。值为 1,代表存在一个节点,值为 0,代表不存在该节点。
输出格式
输出最少暖炉供应数量。
输入样例
1, 1, 0, 1, 1
输出样例
1
数据范围
树的节点数的范围是 [1, 1000]。
思路
首先对于问题,我们依旧先明确输入,输出和处理。我们交给AI试试。
-
用户:帮我将题目的输入,输出和我要做的处理明确
-
AI:好的,让我们明确一下题目的输入、输出以及你需要处理的内容。
-
输入
- 输入格式: 一个数组,按层遍历顺序描述二叉树的节点情况。值为 1 代表存在一个节点,值为 0 代表不存在该节点。
- 示例输入:
{1, 1, 0, 1, 1}
-
输出
- 输出格式: 一个整数,表示使整个村庄都暖和起来至少需要供应的暖炉数量。
- 示例输出:
1
-
你需要做的处理
- 解析输入: 将输入的数组解析成一个二叉树结构。
- 计算最少暖炉数量: 设计一个算法,计算出使整个二叉树的所有节点都暖和起来所需的最少暖炉数量。
根据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,用1和0分别表示节点是否存在。 -
实现:
- 创建根节点
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;
}
总结
核心思想
-
后序遍历(Post-order Traversal)
- 使用后序遍历从叶子节点开始递归地计算子树的覆盖状态,逐步向上解决问题。
- 这种方式能够确保在处理一个节点时,其所有子节点的状态已经被确定。
-
动态规划的状态转移
-
每个节点有三种状态:
0:未被覆盖。1:被覆盖但未放置暖炉。2:放置了暖炉。
-
当前节点的状态取决于其子节点的状态,状态转移的逻辑如下:
- 如果任意一个子节点是
0(未被覆盖),当前节点必须放置暖炉(状态变为2)。 - 如果任意一个子节点是
2(子节点有暖炉),当前节点被覆盖(状态变为1)。 - 如果所有子节点是
1(被覆盖但无暖炉),当前节点未被覆盖(状态变为0)。
- 如果任意一个子节点是
-
-
贪心思想
-
通过后序遍历的特性,优先保证从底层叶子节点到根节点的局部最优:
- 只在必要时(子节点未被覆盖)放置暖炉。
- 每次放置暖炉都覆盖最多的节点(当前节点及其子节点)。
-
这种局部最优的选择能够确保最终解的全局最优。
-
-
特例处理
- 如果递归遍历后,根节点未被覆盖(状态为
0),需要额外放置一个暖炉。这是算法的边界条件,确保根节点始终被覆盖。
- 如果递归遍历后,根节点未被覆盖(状态为
本文通过动态规划与贪心相结合的思想,利用后序遍历高效解决了最少暖炉覆盖问题,主要特点包括:
- 自底向上 的动态规划状态转移。
- 在局部最优的基础上保证全局最优的贪心选择。
- 时间和空间复杂度较低,适合处理大规模的二叉树问题。