Medium
题目描述
给定一个由 n 个节点组成的无向树,节点编号从 0 到 n-1,其中一些节点上有苹果。你需要花费 1 秒时间走过树的一条边。返回从节点 0 出发并回到节点 0,收集树中所有苹果所需的最短时间(以秒为单位)。
无向树的边在数组 edges 中给出,其中 edges[i] = [ai, bi] 表示存在一条连接节点 ai 和 bi 的边。此外,还有一个布尔数组 hasApple,其中 hasApple[i] = true 表示节点 i 有苹果,否则没有苹果。
示例 1:
输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,true,true,false]
输出:8
解释:上图表示给定的树,红色节点有苹果。收集所有苹果的一条最优路径如绿色箭头所示。
示例 2:
输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,true,false,false,true,false]
输出:6
解释:上图表示给定的树,红色节点有苹果。收集所有苹果的一条最优路径如绿色箭头所示。
示例 3:
输入:n = 7, edges = [[0,1],[0,2],[1,4],[1,5],[2,3],[2,6]], hasApple = [false,false,false,false,false,false,false]
输出:0
约束条件:
1 <= n <= 10^5edges.length == n - 1edges[i].length == 20 <= ai < bi <= n - 1hasApple.length == n
提示:
- 如果节点 u 包含苹果,那么从根节点到节点 u 路径上的所有边都必须往返使用(2次)。
- 因此使用深度优先搜索(DFS)来检查一条边是否会被使用。
解题思路
解题思路
这是一个树上的DFS问题。核心思想是:只有当子树中存在苹果时,我们才需要访问该子树。
主要思路
- 建图:根据edges构建邻接表表示的无向树
- DFS遍历:从根节点0开始进行深度优先搜索
- 递归判断:对于每个节点,判断其子树是否需要访问
- 如果当前节点有苹果,则必须访问
- 如果当前节点的任一子树需要访问,则当前节点也需要访问
- 计算时间:每条需要走的边都要往返,所以每条边贡献2秒
关键洞察
- 一条边会被使用当且仅当它连接的子树中有苹果
- 每条被使用的边都需要往返,所以时间代价是边数×2
- 使用DFS可以自底向上地判断每个子树是否需要访问
算法流程
- 构建邻接表
- 从节点0开始DFS
- 对于每个节点,递归检查所有子节点
- 如果子节点的子树需要访问,则当前边被使用,时间+2
- 返回当前子树是否需要访问(有苹果或子树有苹果)
代码实现
class Solution {
public:
int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
vector<vector<int>> graph(n);
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
return dfs(0, -1, graph, hasApple);
}
private:
int dfs(int node, int parent, vector<vector<int>>& graph, vector<bool>& hasApple) {
int time = 0;
for (int child : graph[node]) {
if (child != parent) {
int childTime = dfs(child, node, graph, hasApple);
if (childTime > 0 || hasApple[child]) {
time += 2 + childTime;
}
}
}
return time;
}
};
class Solution:
def minTime(self, n: int, edges: List[List[int]], hasApple: List[bool]) -> int:
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
def dfs(node, parent):
time = 0
for child in graph[node]:
if child != parent:
child_time = dfs(child, node)
if child_time > 0 or hasApple[child]:
time += 2 + child_time
return time
return dfs(0, -1)
public class Solution {
public int MinTime(int n, int[][] edges, IList<bool> hasApple) {
var graph = new List<int>[n];
for (int i = 0; i < n; i++) {
graph[i] = new List<int>();
}
foreach (var edge in edges) {
graph[edge[0]].Add(edge[1]);
graph[edge[1]].Add(edge[0]);
}
return Dfs(0, -1, graph, hasApple);
}
private int Dfs(int node, int parent, List<int>[] graph, IList<bool> hasApple) {
int time = 0;
foreach (int child in graph[node]) {
if (child != parent) {
int childTime = Dfs(child, node, graph, hasApple);
if (childTime > 0 || hasApple[child]) {
time += 2 + childTime;
}
}
}
return time;
}
}
var minTime = function(n, edges, hasApple) {
const graph = Array.from({ length: n }, () => []);
for (const [a, b] of edges) {
graph[a].push(b);
graph[b].push(a);
}
function dfs(node, parent) {
let time = 0;
for (const child of graph[node]) {
if (child !== parent) {
const childTime = dfs(child, node);
if (childTime > 0 || hasApple[child]) {
time += 2 + childTime;
}
}
}
return time;
}
return dfs(0, -1);
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 每个节点访问一次,每条边访问两次 |
| 空间复杂度 | O(n) | 邻接表存储图结构,递归调用栈深度最大为n |