收集树上所有苹果的最少时间
难度:
标签:
题目描述
给你一棵有 n
个节点的无向树,节点编号为 0
到 n-1
,它们中有一些节点有苹果。通过树上的一条边,需要花费 1 秒钟。你从 节点 0 出发,请你返回最少需要多少秒,可以收集到所有苹果,并回到节点 0 。
无向树的边由 edges
给出,其中 edges[i] = [fromi, toi]
,表示有一条边连接 from
和 toi
。除此以外,还有一个布尔数组 hasApple
,其中 hasApple[i] = true
代表节点 i
有一个苹果,否则,节点 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^5
edges.length == n - 1
edges[i].length == 2
0 <= ai < bi <= n - 1
hasApple.length == n
代码结果
运行时间: 104 ms, 内存: 45.0 MB
/*
* 思路:
* 1. 构建树的表示,使用邻接表。
* 2. 使用深度优先搜索(DFS)来遍历树。
* 3. 对于每个节点,如果其子树中有苹果,计算需要的时间并返回。
* 4. 如果一个节点或其子节点有苹果,增加相应的时间。
* 5. 使用Java Stream API来构建树和进行DFS遍历。
*/
import java.util.*;
import java.util.stream.*;
public class SolutionStream {
public int minTime(int n, int[][] edges, List<Boolean> hasApple) {
// 构建邻接表
Map<Integer, List<Integer>> tree = Arrays.stream(edges)
.flatMap(edge -> Stream.of(Map.entry(edge[0], edge[1]), Map.entry(edge[1], edge[0])))
.collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
// 记录访问节点避免重复
boolean[] visited = new boolean[n];
return dfs(0, tree, hasApple, visited);
}
private int dfs(int node, Map<Integer, List<Integer>> tree, List<Boolean> hasApple, boolean[] visited) {
visited[node] = true;
int totalTime = tree.getOrDefault(node, Collections.emptyList()).stream()
.filter(child -> !visited[child])
.mapToInt(child -> {
int timeSpent = dfs(child, tree, hasApple, visited);
return (timeSpent > 0 || hasApple.get(child)) ? timeSpent + 2 : 0;
})
.sum();
return totalTime;
}
}
解释
方法:
题解使用深度优先搜索(DFS)策略,递归地计算树中每个节点所需要的最小时间。基本思路是:从根节点(节点 0)开始,向下遍历每个节点,计算从该节点到其所有子节点收集苹果并返回所需的总时间。如果一个节点或其任何子节点有苹果,该节点返回的时间包括到子节点的往返时间(每个边2秒)。若叶子节点有苹果,则计算其到父节点的往返时间。若叶子节点无苹果,则不需要时间。对于根节点,只需要累加从根节点到其子节点的时间,因为不需要返回根节点。
时间复杂度:
O(n)
空间复杂度:
O(n)
代码细节讲解
🦆
在DFS实现中,为什么要传递父节点的编号`fa`到每次递归调用中?
▷🦆
如何确保不会重复访问同一个节点,避免造成无限循环?
▷🦆
在递归的基案中,为什么叶子节点的处理方式是返回`int(hasApple[u]) * 2`,这里的逻辑是什么?
▷