题目描述
天气越来越冷了,村子里有留守老人缺少照顾,会在这个冬天里挨冻,小华想运用自己的知识帮帮他们。已知村庄里每户人家作为一个节点,这些节点可以组成一个二叉树。我们可以在二叉树的节点上供应暖炉,每个暖炉可以为该节点的父节点、自身及其子节点带来温暖。给定一棵二叉树,求使个村庄都暖和起来至少需要供应多少个暖炉?
输入格式
输入为一个数组,按层遍历顺序描述二叉树的节点情况。值为 1,代表存在一个节点,值为 0,代表不存在该节点。
输出格式
输出最少暖炉供应数量。
输入样例
1, 1, 0, 1, 1
输出样例
1
数据范围
树的节点数的范围是 [1, 1000]。
解题思路
问题转化
本问题可以视为“二叉树摄像头覆盖问题”的变种,目标是通过在二叉树的某些节点放置暖炉,使得所有节点都被覆盖的同时,暖炉的数量最少。
贪心算法设计
本问题适合采用 贪心算法,通过后序遍历(先处理子节点,再处理父节点)的方式决定暖炉的放置位置。
贪心策略
- 如果某节点的子节点需要被覆盖,应在当前节点放置暖炉。
- 如果某节点本身需要被覆盖且其子节点已被覆盖,应在其父节点放置暖炉。
- 优先考虑远离根节点的节点覆盖需求,逐层往根节点推进。
节点状态定义
通过递归的后序遍历,为每个节点定义以下三种状态:
- 状态 0:该节点需要供暖。
- 状态 1:该节点放置了暖炉。
- 状态 2:该节点已被覆盖(由其子节点或父节点的暖炉覆盖)。
根节点处理完成后,如果它仍需要供暖,则在根节点放置一个暖炉。
算法步骤
- 构建二叉树:根据层序遍历数组构建二叉树。
- 后序遍历:从叶子节点开始递归处理,根据子节点的状态决定当前节点是否需要放置暖炉。
- 根节点检查:最后检查根节点是否需要放置暖炉。
- 输出结果:返回总共放置的暖炉数量。
代码实现
二叉树节点类
class TreeNode:
def __init__(self):
self.left = None
self.right = None
主算法实现
class Solution:
def __init__(self):
self.heaters = 0
def minHeaters(self, root: TreeNode) -> int:
def dfs(node: TreeNode) -> int:
if not node:
return 2 # 空节点视为已覆盖
left = dfs(node.left)
right = dfs(node.right)
# 子节点需要供暖
if left == 0 or right == 0:
self.heaters += 1
return 1 # 当前节点放置暖炉
# 子节点已经覆盖
if left == 1 or right == 1:
return 2 # 当前节点已被覆盖
# 子节点未覆盖,当前节点需要供暖
return 0
# 检查根节点
if dfs(root) == 0:
self.heaters += 1
return self.heaters
构建二叉树的辅助函数
from collections import deque
def build_tree(nodes: list) -> TreeNode:
if not nodes or nodes[0] == 0:
return None
root = TreeNode()
queue = deque([root])
index = 1
n = len(nodes)
while queue and index < n:
current = queue.popleft()
# 左子节点
if index < n and nodes[index] == 1:
current.left = TreeNode()
queue.append(current.left)
index += 1
# 右子节点
if index < n and nodes[index] == 1:
current.right = TreeNode()
queue.append(current.right)
index += 1
return root
完整代码
from typing import Optional
from collections import deque
# 定义二叉树节点
class TreeNode:
def __init__(self):
self.left: Optional['TreeNode'] = None
self.right: Optional['TreeNode'] = None
# 主解决方案类
class Solution:
def __init__(self):
self.heaters = 0
def minHeaters(self, root: Optional[TreeNode]) -> int:
# 定义后序遍历函数
def dfs(node: Optional[TreeNode]) -> int:
if not node:
return 2 # 空节点视为已覆盖
left = dfs(node.left)
right = dfs(node.right)
if left == 0 or right == 0:
self.heaters += 1
return 1 # 当前节点放置暖炉
if left == 1 or right == 1:
return 2 # 当前节点被覆盖
return 0 # 当前节点需要被覆盖
if dfs(root) == 0:
self.heaters += 1
return self.heaters
# 构建二叉树的函数
def build_tree(nodes: list) -> Optional[TreeNode]:
if not nodes or nodes[0] == 0:
return None
root = TreeNode()
queue = deque([root])
index = 1
n = len(nodes)
while queue and index < n:
current = queue.popleft()
# 处理左孩子
if index < n and nodes[index] == 1:
current.left = TreeNode()
queue.append(current.left)
index += 1
# 处理右孩子
if index < n and nodes[index] == 1:
current.right = TreeNode()
queue.append(current.right)
index += 1
return root
def solution(nodes):
tree = build_tree(nodes)
return Solution().minHeaters(tree)
if __name__ == "__main__":
# You can add more test cases here
print(solution([1, 1, 0, 1, 1]) == 1 )
print(solution([1,0,1,1,0,1,0,1,0,1,0,0,1,1]) == 3 )
print(solution([1,1,0,0,1,1,0,0,1,0,1,1,0,0,1]) == 3 )
示例运行
输入
1, 1, 0, 1, 1
运行过程
构建的二叉树结构:
1
/
1
/ \
1 1
后序遍历:
- 从叶子节点
1开始,状态为0,需要供暖。 - 父节点
1放置暖炉,状态变为1。 - 根节点被覆盖,状态为
2。
最终结果:需要 1 个暖炉。
输出
1
时间与空间复杂度分析
时间复杂度
- 二叉树构建:O(N),其中 N 是节点数量。
- 后序遍历:O(N),每个节点只被访问一次。
总复杂度:O(N)。
空间复杂度
- 递归栈深度:O(H),其中 H 是树的高度,最坏情况下为 O(N)(退化为链表)。
- 队列空间:O(N)。
总复杂度:O(N)。
总结与反思
- 该算法思路可以推广到类似的“覆盖问题”:
- 如二叉树摄像头问题。
- 图论中基于顶点的覆盖问题。
- 在更大规模或动态变化的树中,可考虑动态规划优化递归。