leetcode
leetcode 2451 ~ 2500
统计树中的合法路径数目

统计树中的合法路径数目

难度:

标签:

题目描述

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

Return the number of valid paths in the tree.

A path (a, b) is valid if there exists exactly one prime number among the node labels in the path from a to b.

Note that:

  • The path (a, b) is a sequence of distinct nodes starting with node a and ending with node b such that every two adjacent nodes in the sequence share an edge in the tree.
  • Path (a, b) and path (b, a) are considered the same and counted only once.

 

Example 1:

Input: n = 5, edges = [[1,2],[1,3],[2,4],[2,5]]
Output: 4
Explanation: The pairs with exactly one prime number on the path between them are: 
- (1, 2) since the path from 1 to 2 contains prime number 2. 
- (1, 3) since the path from 1 to 3 contains prime number 3.
- (1, 4) since the path from 1 to 4 contains prime number 2.
- (2, 4) since the path from 2 to 4 contains prime number 2.
It can be shown that there are only 4 valid paths.

Example 2:

Input: n = 6, edges = [[1,2],[1,3],[2,4],[3,5],[3,6]]
Output: 6
Explanation: The pairs with exactly one prime number on the path between them are: 
- (1, 2) since the path from 1 to 2 contains prime number 2.
- (1, 3) since the path from 1 to 3 contains prime number 3.
- (1, 4) since the path from 1 to 4 contains prime number 2.
- (1, 6) since the path from 1 to 6 contains prime number 3.
- (2, 4) since the path from 2 to 4 contains prime number 2.
- (3, 6) since the path from 3 to 6 contains prime number 3.
It can be shown that there are only 6 valid paths.

 

Constraints:

  • 1 <= n <= 105
  • edges.length == n - 1
  • edges[i].length == 2
  • 1 <= ui, vi <= n
  • The input is generated such that edges represent a valid tree.

代码结果

运行时间: 234 ms, 内存: 49.3 MB


// Java Stream Solution
// 思路:
// 1. 使用递归的方式进行深度优先搜索 (DFS) 遍历树。
// 2. 使用 Java Stream 进行集合操作来处理邻接节点的递归调用。

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

public class LegalPathCountStream {
    private List<Integer>[] tree;
    private boolean[] isPrime;
    private boolean[] visited;
    private int n;
    private int legalPathCount;

    public int countLegalPaths(int n, int[][] edges) {
        this.n = n;
        this.tree = new ArrayList[n + 1];
        this.visited = new boolean[n + 1];
        this.isPrime = new boolean[n + 1];
        this.legalPathCount = 0;

        // 初始化树结构
        for (int i = 1; i <= n; i++) {
            tree[i] = new ArrayList<>();
        }
        Arrays.stream(edges).forEach(edge -> {
            tree[edge[0]].add(edge[1]);
            tree[edge[1]].add(edge[0]);
        });

        // 预先标记所有质数
        sieveOfEratosthenes(n);

        // 从节点 1 开始 DFS 遍历
        dfs(1, -1);
        return legalPathCount;
    }

    private void sieveOfEratosthenes(int n) {
        Arrays.fill(isPrime, true);
        isPrime[0] = isPrime[1] = false;
        for (int i = 2; i * i <= n; i++) {
            if (isPrime[i]) {
                for (int j = i * i; j <= n; j += i) {
                    isPrime[j] = false;
                }
            }
        }
    }

    private void dfs(int node, int parent) {
        visited[node] = true;
        tree[node].stream()
            .filter(neighbor -> neighbor != parent && !visited[neighbor])
            .forEach(neighbor -> {
                if (isPrime[node] && !isPrime[neighbor]) {
                    legalPathCount++;
                }
                dfs(neighbor, node);
            });
    }
}

// 使用示例
// LegalPathCountStream solution = new LegalPathCountStream();
// int result = solution.countLegalPaths(n, edges);

解释

方法:

此题解采用了埃拉托斯特尼筛法来预处理质数,并使用并查集与图的邻接表来识别和处理路径。首先,通过筛法建立一个质数查找表PRIME,以便快速判断节点编号是否为质数。然后,遍历所有边,如果一条边的两个节点一个为质数一个非质数,则在图中添加这条边;如果两个节点都非质数,则通过并查集合并这两个节点。最后,计算图中每个联通分量的路径数量,只考虑含有质数节点的联通分量,使用组合公式计算每个联通分量中任意两点间路径的总数。

时间复杂度:

O(N log log N)

空间复杂度:

O(N)

代码细节讲解

🦆
题解中提到的`并查集`是如何在此问题的上下文中帮助合并非质数节点以及它的具体作用是什么?
在此问题中,我们需要处理和统计只包含至少一个质数节点的合法路径。并查集(Union-Find)是一种数据结构,用于高效地处理和查询元素间的连通性问题。在本题的上下文中,我们使用并查集来合并所有非质数节点,因为我们只关心包含质数节点的联通分量。当两个节点都是非质数时,我们将它们合并成一个联通分量。这样一来,我们可以忽略纯非质数的联通分量,从而专注于只包含至少一个质数节点的分量。这种方法降低了问题的复杂度,使我们能够更直接地计算结果。
🦆
为什么在处理图时只在一条边的两个节点一个为质数一个非质数的情况下添加这条边?如果两个节点都是质数怎么处理?
在本题的解法中,我们的目标是找出所有包含至少一个质数节点的合法路径。当一条边连接一个质数节点和一个非质数节点时,这条边可能是连接两个不同质数节点的不同联通分量的桥梁,因此需要添加到图中以便后续的路径计算。如果两个节点都是质数,根据题解中的策略,这种情况并没有明确说明需要特别处理。在实际应用中,可以考虑是否需要将两个质数节点视为一个潜在的合法路径的起点和终点,这取决于具体问题的需求和定义。
🦆
题解中使用了`埃拉托斯特尼筛法`预处理质数,能否解释一下这种筛法的原理以及为什么它是有效的?
埃拉托斯特尼筛法(Sieve of Eratosthenes)是一种高效的算法,用于找出小于或等于某个整数的所有质数。其原理是从2开始,首先标记2的倍数(除了2本身)为非质数,然后找到下一个未被标记的数字(它一定是质数),再标记其所有倍数为非质数。这个过程重复进行,直到达到指定的数。这种方法之所以有效,是因为它从小到大逐步筛除了合数的同时,保留了质数的标记,且每个合数都被其最小的质因数筛除,从而避免了重复工作。
🦆
计算联通分量中任意两点间路径的总数时使用了一个lambda函数`fn`,能否具体解释这个函数的计算逻辑及其如何应用于解题中?
在题解中,lambda函数`fn`被用来计算每个联通分量中任意两点间路径的总数。这个函数首先计算单个节点的贡献(每个节点都可以单独作为一个路径的起点或终点),然后计算两两节点间组合的路径数。具体来说,`fn`函数中的`sum(np)`计算的是每个节点单独作为路径的贡献,而`sum(a * b for a, b in zip(np[1:], accumulate(np)))`计算的是所有可能的节点对组合,其中每对于每对节点 (a, b),其路径数为 a 和 b 的节点数乘积,因为可以从 a 的任意一个节点开始到 b 的任意一个节点结束。通过这种方式,我们能够利用联通分量中的结构信息来快速计算路径总数,从而有效解决问题。

相关问题