leetcode
leetcode 2451 ~ 2500
可以被 K 整除连通块的最大数目

可以被 K 整除连通块的最大数目

难度:

标签:

题目描述

There is an undirected tree with n nodes labeled from 0 to n - 1. You are given the integer n and a 2D integer array edges of length n - 1, where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree.

You are also given a 0-indexed integer array values of length n, where values[i] is the value associated with the ith node, and an integer k.

A valid split of the tree is obtained by removing any set of edges, possibly empty, from the tree such that the resulting components all have values that are divisible by k, where the value of a connected component is the sum of the values of its nodes.

Return the maximum number of components in any valid split.

 

Example 1:

Input: n = 5, edges = [[0,2],[1,2],[1,3],[2,4]], values = [1,8,1,4,4], k = 6
Output: 2
Explanation: We remove the edge connecting node 1 with 2. The resulting split is valid because:
- The value of the component containing nodes 1 and 3 is values[1] + values[3] = 12.
- The value of the component containing nodes 0, 2, and 4 is values[0] + values[2] + values[4] = 6.
It can be shown that no other valid split has more than 2 connected components.

Example 2:

Input: n = 7, edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], values = [3,0,6,1,5,2,1], k = 3
Output: 3
Explanation: We remove the edge connecting node 0 with 2, and the edge connecting node 0 with 1. The resulting split is valid because:
- The value of the component containing node 0 is values[0] = 3.
- The value of the component containing nodes 2, 5, and 6 is values[2] + values[5] + values[6] = 9.
- The value of the component containing nodes 1, 3, and 4 is values[1] + values[3] + values[4] = 6.
It can be shown that no other valid split has more than 3 connected components.

 

Constraints:

  • 1 <= n <= 3 * 104
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • values.length == n
  • 0 <= values[i] <= 109
  • 1 <= k <= 109
  • Sum of values is divisible by k.
  • The input is generated such that edges represents a valid tree.

代码结果

运行时间: 173 ms, 内存: 34.9 MB


/*
 * 思路:
 * 1. 使用DFS和Java Stream来计算每个节点的子树值和,并检查是否可以被k整除。
 * 2. 对于能被k整除的子树,增加连通块数。
 */

import java.util.*;
import java.util.stream.Collectors;

public class Solution {
    public int maxKDivisibleComponents(int n, int[][] edges, int[] values, int k) {
        // 构建图
        Map<Integer, List<Integer>> graph = Arrays.stream(edges)
            .flatMap(e -> Stream.of(Map.entry(e[0], e[1]), Map.entry(e[1], e[0])))
            .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

        int[] sum = new int[n];
        int[] res = new int[1];
        boolean[] visited = new boolean[n];

        // 从根节点0开始DFS
        dfs(0, graph, values, k, sum, visited, res);

        return res[0];
    }

    private int dfs(int node, Map<Integer, List<Integer>> graph, int[] values, int k, int[] sum, boolean[] visited, int[] res) {
        visited[node] = true;
        sum[node] = values[node];

        for (int neighbor : graph.getOrDefault(node, Collections.emptyList())) {
            if (!visited[neighbor]) {
                sum[node] += dfs(neighbor, graph, values, k, sum, visited, res);
            }
        }

        // 如果子树和可以被k整除
        if (sum[node] % k == 0) {
            res[0]++;
            return 0;
        }

        return sum[node];
    }
}

解释

方法:

题解采用了深度优先搜索(DFS)来遍历树,并计算各个子树的节点值之和。在遍历过程中,每当一个子树的节点值之和可以被k整除时,就将结果计数器(ans)加1。这样,计数器最终的值就是可以分割成的最大连通块数。函数dfs(x, fa)用于从节点x开始,避开父节点fa,递归地计算子树的值之和。如果子树的值之和可以被k整除,则意味着可以在此处断开连接,从而形成一个新的连通块。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
如何处理`values`数组中的负数或零值,这些特殊值对计算连通块的值之和有什么影响?
在计算连通块的值之和时,`values`数组中的负数或零值按照其算术值正常处理。这意味着,负数会减少总和,而零值不会影响总和。在求和的过程中,只要最终的子树值之和能被`k`整除,无论是由于包含负数、零值还是正数,都会被视为有效的连通块。因此,这些特殊值不会影响连通块计数的逻辑,仅影响计算的数值结果。
🦆
题解中提到如果子树的值之和可以被k整除则形成一个新的连通块,如果整棵树的值之和也能被k整除,该如何调整答案以避免重复计数?
在题解的实现中,如果整棵树的值之和也可以被`k`整除,那么整棵树本身也被错误地计为一个可以分割的连通块。为了纠正这一点,我们需要从最终的结果中减去1。这是因为题目要求的是可以分割的连通块的最大数目,而整棵树本身不能被视为分割出的连通块。因此,在返回答案之前应减去1,确保不重复计算整棵树。
🦆
为什么在DFS递归函数中,子树的值之和如果能被k整除就立即增加答案计数,而不是等到整个递归完成后再统一处理?
在DFS递归过程中立即增加答案计数允许我们在每次递归调用返回时就确定是否形成了一个新的连通块。这种方法利用了递归的自然结构,即从底部到顶部逐步构建解决方案的过程。如果等到整个递归完成后再统一处理,我们将需要额外的数据结构来存储每个子树的计算结果,这样会增加算法的复杂性和可能的错误源。因此,贪心地在每次符合条件的递归返回时更新结果是更直接和高效的处理方式。
🦆
如果树的结构为一条链,DFS的递归深度将达到O(n),有没有办法优化算法以支持非常大的树?
对于非常大的树,特别是当树的结构呈现为一条链时,递归的深度优先搜索可能会导致深度递归栈溢出。为了优化这种情况,可以考虑使用迭代的深度优先搜索(使用显式栈)或广度优先搜索(BFS)。这些迭代方法避免了使用递归调用栈,从而减少了栈溢出的风险。此外,对于特别大的数据结构,可以考虑使用尾递归优化,虽然这在Python中不是自动的,但可以通过手动优化递归逻辑来实现。

相关问题