Count Valid Paths in a Tree
HARDDescription
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 nodeaand ending with nodebsuch 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 <= 105edges.length == n - 1edges[i].length == 21 <= ui, vi <= n- The input is generated such that
edgesrepresent a valid tree.
Approaches
Checkout 2 different approaches to solve Count Valid Paths in a Tree. Click on different approaches to view the approach and algorithm in detail.
Brute-Force Traversal from Each Node
This approach iterates through every possible starting node in the tree. For each starting node, it performs a traversal (like Depth-First Search or Breadth-First Search) to find all possible paths originating from it. During the traversal, it keeps track of the number of prime nodes encountered on the current path. If a path to a destination node contains exactly one prime number, it's counted as a valid path.
Algorithm
- Create a boolean array
isPrimeof sizen+1and populate it using the Sieve of Eratosthenes. - Build an adjacency list for the tree from the
edgesarray. - Initialize a global counter
validPathsto 0. - Iterate through each node
ifrom 1 ton. - For each
i, start a DFS traversaldfs(u, parent, primeCount)fromu=i,parent=-1,primeCount=0. - In the
dfs(u, p, pc)function: a. Update the prime count:new_pc = pc + (isPrime[u] ? 1 : 0). b. Ifnew_pc > 1, stop exploring this path and return. c. Ifnew_pc == 1, it means the path from the start nodeitouis valid. IncrementvalidPaths. d. For each neighborvofu(wherev != p), recursively calldfs(v, u, new_pc). - After the outer loop finishes, return
validPaths / 2to correct for double counting.
First, we need a way to quickly check if a number is prime. We can pre-compute primality for all numbers from 1 to n using the Sieve of Eratosthenes and store the results in a boolean array.
Next, we build an adjacency list representation of the tree from the given edges.
The main part of the algorithm involves a nested loop. The outer loop iterates through each node i from 1 to n, considering it as a potential starting point of a path.
From each starting node i, we initiate a Depth-First Search (DFS). The DFS function, say dfs(u, p, primeCount), will explore the tree, where u is the current node, p is its parent (to avoid going backward), and primeCount is the count of prime numbers on the path from the starting node i to u.
Inside the DFS, we first update primeCount based on whether the current node u is prime.
If the primeCount for the path from i to u is exactly 1, we've found a valid path (i, u). We've found a valid path (i, u). We increment our total count.
The DFS continues recursively to all neighbors of u (except its parent), but only if the primeCount does not exceed 1. If it's already 2 or more, any further extension of the path will also have at least 2 primes, so we can prune this search branch.
Since this process counts each path (a, b) twice (once when starting from a and once from b), the final result is the total count divided by 2.
class Solution {
private List<List<Integer>> adj;
private boolean[] isPrime;
private long count = 0;
private int n;
public long countPaths(int n, int[][] edges) {
this.n = n;
sieve(n);
adj = new ArrayList<>();
for (int i = 0; i <= n; i++) {
adj.add(new ArrayList<>());
}
for (int[] edge : edges) {
adj.get(edge[0]).add(edge[1]);
adj.get(edge[1]).add(edge[0]);
}
for (int i = 1; i <= n; i++) {
// We only need to start from a prime or a node adjacent to a prime
// to find all paths, but for simplicity of brute force, we start from all.
dfs(i, -1, 0);
}
return count / 2;
}
private void dfs(int u, int p, int primeCount) {
if (isPrime[u]) {
primeCount++;
}
if (primeCount > 1) {
return;
}
// If start node is not u, and path is valid, count it.
if (primeCount == 1) {
count++;
}
for (int v : adj.get(u)) {
if (v != p) {
dfs(v, u, primeCount);
}
}
}
private void sieve(int n) {
isPrime = new boolean[n + 1];
if (n >= 2) {
java.util.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;
}
}
}
}
}
Complexity Analysis
Pros and Cons
- Relatively simple to understand and implement compared to more optimized solutions.
- Correctly solves the problem for small
n.
- Highly inefficient due to redundant computations. The traversal from each node re-explores large parts of the tree.
- Will result in a "Time Limit Exceeded" error for the given constraints (
nup to 10^5).
Code Solutions
Checking out 3 solutions in different languages for Count Valid Paths in a Tree. Click on different languages to view the code.
class PrimeTable { private final boolean [] prime ; public PrimeTable ( int n ) { prime = new boolean [ n + 1 ]; Arrays . fill ( prime , true ); prime [ 0 ] = false ; prime [ 1 ] = false ; for ( int i = 2 ; i <= n ; ++ i ) { if ( prime [ i ]) { for ( int j = i + i ; j <= n ; j += i ) { prime [ j ] = false ; } } } } public boolean isPrime ( int x ) { return prime [ x ]; } } class UnionFind { private final int [] p ; private final int [] size ; public UnionFind ( int n ) { p = new int [ n ]; size = new int [ n ]; for ( int i = 0 ; i < n ; ++ i ) { p [ i ] = i ; size [ i ] = 1 ; } } public int find ( int x ) { if ( p [ x ] != x ) { p [ x ] = find ( p [ x ]); } return p [ x ]; } public boolean union ( int a , int b ) { int pa = find ( a ), pb = find ( b ); if ( pa == pb ) { return false ; } if ( size [ pa ] > size [ pb ]) { p [ pb ] = pa ; size [ pa ] += size [ pb ]; } else { p [ pa ] = pb ; size [ pb ] += size [ pa ]; } return true ; } public int size ( int x ) { return size [ find ( x )]; } } class Solution { private static final PrimeTable PT = new PrimeTable ( 100010 ); public long countPaths ( int n , int [][] edges ) { List < Integer >[] g = new List [ n + 1 ]; Arrays . setAll ( g , i -> new ArrayList <>()); UnionFind uf = new UnionFind ( n + 1 ); for ( int [] e : edges ) { int u = e [ 0 ], v = e [ 1 ]; g [ u ]. add ( v ); g [ v ]. add ( u ); if (! PT . isPrime ( u ) && ! PT . isPrime ( v )) { uf . union ( u , v ); } } long ans = 0 ; for ( int i = 1 ; i <= n ; ++ i ) { if ( PT . isPrime ( i )) { long t = 0 ; for ( int j : g [ i ]) { if (! PT . isPrime ( j )) { long cnt = uf . size ( j ); ans += cnt ; ans += cnt * t ; t += cnt ; } } } } return ans ; } }Video Solution
Watch the video walkthrough for Count Valid Paths in a Tree
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.