创建价值相同的连通块
难度:
标签:
题目描述
有一棵 n
个节点的无向树,节点编号为 0
到 n - 1
。
给你一个长度为 n
下标从 0 开始的整数数组 nums
,其中 nums[i]
表示第 i
个节点的值。同时给你一个长度为 n - 1
的二维整数数组 edges
,其中 edges[i] = [ai, bi]
表示节点 ai
与 bi
之间有一条边。
你可以 删除 一些边,将这棵树分成几个连通块。一个连通块的 价值 定义为这个连通块中 所有 节点 i
对应的 nums[i]
之和。
你需要删除一些边,删除后得到的各个连通块的价值都相等。请返回你可以删除的边数 最多 为多少。
示例 1:
输入:nums = [6,2,2,2,6], edges = [[0,1],[1,2],[1,3],[3,4]] 输出:2 解释:上图展示了我们可以删除边 [0,1] 和 [3,4] 。得到的连通块为 [0] ,[1,2,3] 和 [4] 。每个连通块的价值都为 6 。可以证明没有别的更好的删除方案存在了,所以答案为 2 。
示例 2:
输入:nums = [2], edges = [] 输出:0 解释:没有任何边可以删除。
提示:
1 <= n <= 2 * 104
nums.length == n
1 <= nums[i] <= 50
edges.length == n - 1
edges[i].length == 2
0 <= edges[i][0], edges[i][1] <= n - 1
edges
表示一棵合法的树。
代码结果
运行时间: 220 ms, 内存: 32.7 MB
/*
* 思路:
* 1. 计算树中所有节点值的总和。
* 2. 找到总和的所有可能的除数(这些除数是可能的连通块价值)。
* 3. 使用DFS来验证树是否能分成这些连通块,使每个块的价值相等。
* 4. 尝试所有可能的块价值,找出最大可以删除的边数。
*/
import java.util.*;
import java.util.stream.*;
public class EqualTreePartitionStream {
public int maxNumEdgesToRemove(int[] nums, int[][] edges) {
int totalSum = Arrays.stream(nums).sum();
List<Integer> divisors = IntStream.rangeClosed(1, (int) Math.sqrt(totalSum))
.filter(i -> totalSum % i == 0)
.boxed()
.flatMap(i -> Stream.of(i, totalSum / i))
.distinct()
.collect(Collectors.toList());
Map<Integer, List<Integer>> graph = Arrays.stream(edges)
.flatMap(edge -> Stream.of(new AbstractMap.SimpleEntry<>(edge[0], edge[1]),
new AbstractMap.SimpleEntry<>(edge[1], edge[0])))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
int maxEdges = 0;
for (int divisor : divisors) {
if (divisor == 0 || divisor == totalSum) continue;
Set<Integer> visited = new HashSet<>();
if (canPartition(graph, nums, 0, visited, divisor) == totalSum / divisor) {
maxEdges = Math.max(maxEdges, visited.size() - 1);
}
}
return maxEdges;
}
private int canPartition(Map<Integer, List<Integer>> graph, int[] nums, int node, Set<Integer> visited, int target) {
visited.add(node);
int currentSum = nums[node];
for (int neighbor : graph.get(node)) {
if (!visited.contains(neighbor)) {
currentSum += canPartition(graph, nums, neighbor, visited, target);
}
}
if (currentSum == target) {
return 0;
}
return currentSum;
}
public static void main(String[] args) {
EqualTreePartitionStream etps = new EqualTreePartitionStream();
int[] nums = {6, 2, 2, 2, 6};
int[][] edges = {{0, 1}, {1, 2}, {1, 3}, {3, 4}};
System.out.println(etps.maxNumEdgesToRemove(nums, edges)); // 输出:2
}
}
解释
方法:
该题解通过DFS和贪心算法的结合来解决问题。首先,题解构建了一个邻接列表表示的图来描述树。在解题的核心方法中,题解尝试将树分割成具有相同总和的多个连通块。这是通过尝试每个可能的连通块的目标和(即树节点值总和的因子)来完成的。对于每个目标和,使用DFS检查是否可以通过删除边来形成大小为目标和的连通块。如果从根节点开始的DFS返回0,表示可以形成这样的连通块。DFS函数在遍历时累积子树的值,如果子树的值等于目标和,则可以视为一个独立的连通块,因此该子树的贡献重置为0。如果子树的值超过了目标和,直接返回-1表示不可能形成所需的连通块。通过这种方式,题解实现了对可能删除的最大边数的检测。
时间复杂度:
O(n * sqrt(total))
空间复杂度:
O(n)
代码细节讲解
🦆
题中提到用DFS检查是否可以通过删除边来形成大小为目标和的连通块,具体是如何通过DFS实现这一检查的?
▷🦆
在DFS过程中,遇到子树总和等于目标和时为何将返回值设为0,这样的处理有什么特殊含义吗?
▷🦆
题解中提到如果子树的值超过目标和就返回-1,这种情况下是否意味着整个分割尝试失败,还是只是当前分割路径不可行?
▷🦆
DFS函数中,参数`fa`代表什么意义?为什么需要这个参数?
▷