Given a tree of N nodes and an integer K, the task is to find the total number of paths having length K.
Examples:
Input: N = 5, K = 2
tree = 1
/ \
2 5
/ \
3 4
Output: 4
Explanation: The paths having length 2 are
1 – 2 – 3, 1 – 2 – 4, 2 – 1 – 5, 3 – 2 – 4Input: N = 2, K = 2
tree = 1
/
2
Output: 0
Explanation: There is no path in the tree having length 2.
Intuition: The main idea is to find the K-length paths from each node and add them.
- Find the number of K-length paths ‘originating’ from a given node ‘node’. ‘Originating’ here means, ‘node’ will have the smallest depth among all the nodes in the path. For example, 2-length paths originating from 1 are shown in the below diagram.
- Sum above value for all the nodes and it will be the required answer.
Naive Approach: To compute the K-length paths originating from ‘node’ two DFS are used. Say this entire process is: paths_originating_from(node)
- Suppose ‘node’ has multiple children and currently child ‘u’ is being processed.
- For all the previous children, the frequency of nodes at a particular depth has been calculated. More formally, freq[d] gives the number of nodes at depth ‘d’ when only children of ‘node’ before ‘u’ have been processed.
- If there is a node ‘x’ at depth ‘d’, number of K length paths originating from ‘node’ and passing through ‘x’ will be freq[K – d].
- The first DFSF would contribute to the final answer, and the second DFS would update the freq[] array for future use.
- Sum-up ‘paths_originating_from(node)’ for all nodes of the tree, this will be the required answer.
See the image below to understand the 2nd point better.
Below is the implementation of the above approach.
C++
// C++ code to implement above approach #include <bits/stdc++.h> using namespace std; int mx_depth = 0, ans = 0; int N, K; vector< int > freq; vector<vector< int > > g; // This dfs is responsible for calculating ans // and updating freq vector void dfs( int node, int par, int depth, bool contri) { if (depth > K) return ; mx_depth = max(mx_depth, depth); if (contri) { ans += freq[K - depth]; } else { freq[depth]++; } for ( auto nebr : g[node]) { if (nebr != par) { dfs(nebr, node, depth + 1, contri); } } } // Function to calculate K length paths // originating from node void paths_originating_from( int node, int par) { mx_depth = 0; freq[0] = 1; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it for ( auto nebr : g[node]) { if (nebr != par) { dfs(nebr, node, 1, true ); dfs(nebr, node, 1, false ); } } // Re-initialize freq vector for ( int i = 0; i <= mx_depth; ++i) { freq[i] = 0; } // Repeat the same for children for ( auto nebr : g[node]) { if (nebr != par) { paths_originating_from(nebr, node); } } } // Utility method to add edges to tree void edge( int a, int b) { a--; b--; g[a].push_back(b); g[b].push_back(a); } // Driver code int main() { N = 5, K = 2; freq = vector< int >(N); g = vector<vector< int > >(N); edge(1, 2); edge(1, 5); edge(2, 3); edge(2, 4); paths_originating_from(0, -1); cout << ans << endl; } |
Java
import java.io.*; import java.util.*; public class Main { static int N, K, mx_depth = 0 , ans = 0 ; ; static void edge( int a, int b, ArrayList<ArrayList<Integer> > g) { a--; b--; g.get(a).add(b); g.get(b).add(a); } // This dfs is responsible for calculating ans // and updating freq vector static void dfs( int node, int par, int depth, boolean contri, ArrayList<Integer> freq, ArrayList<ArrayList<Integer> > g) { if (depth > K) return ; mx_depth = Math.max(mx_depth, depth); if (contri) { ans += freq.get(K - depth); } else { freq.set(depth, freq.get(depth) + 1 ); } for ( int i = 0 ; i < g.get(node).size(); i++) { int nebr = g.get(node).get(i); if (nebr != par) { dfs(nebr, node, depth + 1 , contri, freq, g); } } } // Function to calculate K length paths // originating from node static void paths_originating_from( int node, int par, ArrayList<Integer> freq, ArrayList<ArrayList<Integer> > g) { mx_depth = 0 ; freq.set( 0 , 1 ); // For every not-removed nebr, // calculate its contribution, // then update freq vector for it for ( int i = 0 ; i < g.get(node).size(); i++) { int nebr = g.get(node).get(i); if (nebr != par) { dfs(nebr, node, 1 , true , freq, g); dfs(nebr, node, 1 , false , freq, g); } } // Re-initialize freq vector for ( int i = 0 ; i <= mx_depth; ++i) { freq.set(i, 0 ); } // Repeat the same for children for ( int i = 0 ; i < g.get(node).size(); i++) { int nebr = g.get(node).get(i); if (nebr != par) { paths_originating_from(nebr, node, freq, g); } } } public static void main(String[] args) { N = 5 ; K = 2 ; ArrayList<Integer> freq = new ArrayList<Integer>(); for ( int i = 0 ; i < N; i++) freq.add( 0 ); ArrayList<ArrayList<Integer> > g = new ArrayList<ArrayList<Integer> >(); for ( int i = 0 ; i < N; i++) g.add( new ArrayList<Integer>()); edge( 1 , 2 , g); edge( 1 , 5 , g); edge( 2 , 3 , g); edge( 2 , 4 , g); paths_originating_from( 0 , - 1 , freq, g); System.out.println(ans); } } // This code is contributed by garg28harsh. |
Python3
# Python code to implement above approach mx_depth = 0 ans = 0 N = 5 K = 2 freq = [ 0 ] * N g = [[] for _ in range (N)] # This dfs is responsible for calculating ans # and updating freq vector def dfs(node, par, depth, contri): global mx_depth, ans, freq if depth > K: return mx_depth = max (mx_depth, depth) if contri: ans + = freq[K - depth] else : freq[depth] + = 1 for nebr in g[node]: if nebr ! = par: dfs(nebr, node, depth + 1 , contri) # Function to calculate K length paths # originating from node def paths_originating_from(node, par): global mx_depth, freq mx_depth = 0 freq[ 0 ] = 1 # For every not-removed nebr, # calculate its contribution, # then update freq vector for it for nebr in g[node]: if nebr ! = par: dfs(nebr, node, 1 , True ) dfs(nebr, node, 1 , False ) # Re-initialize freq vector freq = [ 0 ] * (mx_depth + 1 ) # Repeat the same for children for nebr in g[node]: if nebr ! = par: paths_originating_from(nebr, node) # Utility method to add edges to tree def edge(a, b): a - = 1 b - = 1 g[a].append(b) g[b].append(a) # Driver code edge( 1 , 2 ) edge( 1 , 5 ) edge( 2 , 3 ) edge( 2 , 4 ) paths_originating_from( 0 , - 1 ) print (ans) |
Javascript
// Javascript code to implement above approach let max_Depth = 0, ans = 0, N, K, freq = []; let g = new Array(1000).fill(0).map(() => new Array(0)) // This dfs is responsible for calculating ans // and updating freq vector function dfs(node, par, depth, contri) { if (depth > K) return ; mx_depth = Math.max(mx_depth, depth); if (contri) { ans += freq[K - depth]; } else { freq[depth]++; } for (let nebr of g[node]) { if (nebr != par) { dfs(nebr, node, depth + 1, contri); } } } // Function to calculate K length paths // originating from node function paths_originating_from(node, par) { mx_depth = 0; freq[0] = 1; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it for (let nebr of g[node]) { if (nebr != par) { dfs(nebr, node, 1, true ); dfs(nebr, node, 1, false ); } } // Re-initialize freq vector for (let i = 0; i <= mx_depth; ++i) { freq[i] = 0; } // Repeat the same for children for (let nebr of g[node]) { if (nebr != par) { paths_originating_from(nebr, node); } } } // Utility method to add edges to tree function edge(a, b) { a--; b--; g[a].push(b); g[b].push(a); } // Driver code N = 5; K = 2; freq = new Array(N).fill(0); g = new Array(1000).fill(0).map(() => new Array()); edge(1, 2); edge(1, 5); edge(2, 3); edge(2, 4); paths_originating_from(0, -1); console.log(ans) |
C#
// C# code to implement above approach using System; using System.Collections.Generic; class Program { static int mx_depth = 0, ans = 0; static int N, K; static List< int > freq; static List<List< int > > g; // This dfs is responsible for calculating ans // and updating freq vector static void dfs( int node, int par, int depth, bool contri) { if (depth > K) return ; mx_depth = Math.Max(mx_depth, depth); if (contri) { ans += freq[K - depth]; } else { freq[depth]++; } foreach ( int nebr in g[node]) { if (nebr != par) { dfs(nebr, node, depth + 1, contri); } } } // Function to calculate K length paths // originating from node static void paths_originating_from( int node, int par) { mx_depth = 0; freq[0] = 1; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it foreach ( int nebr in g[node]) { if (nebr != par) { dfs(nebr, node, 1, true ); dfs(nebr, node, 1, false ); } } // Re-initialize freq vector for ( int i = 0; i <= mx_depth; ++i) { freq[i] = 0; } // Repeat the same for children foreach ( int nebr in g[node]) { if (nebr != par) { paths_originating_from(nebr, node); } } } // Utility method to add edges to tree static void edge( int a, int b) { a--; b--; g[a].Add(b); g[b].Add(a); } // Driver code static void Main() { N = 5; K = 2; freq = new List< int >(N); g = new List<List< int > >(N); for ( int i = 0; i < N; i++) { freq.Add(0); g.Add( new List< int >()); } edge(1, 2); edge(1, 5); edge(2, 3); edge(2, 4); paths_originating_from(0, -1); Console.WriteLine(ans); } } // This code is contributed by rutikbhosale. |
4
Time Complexity: O(N * H) where H is the height of the tree which can be N at max
Auxiliary Space: O(N)
Efficient Approach: This approach is based on the concept of Centroid Decomposition. The steps are as follows:
- Find the centroid of current tree T.
- All ‘not-removed’ nodes reachable from T belong to its sub-tree. Call paths_originating_from(T), then mark T as ‘removed’.
- Repeat the above process for all ‘not-removed’ neighbors of T.
The following figure shows a tree with current centroid and its sub-tree. Note that nodes with thick borders have already been selected as centroids previously and do not belong to the sub-tree of the current centroid.
Below is the implementation of the above approach.
C++
// C++ code to implement above approach #include <bits/stdc++.h> using namespace std; // Struct for centroid decomposition struct CD { // 1. mx_depth will be used to store // the height of a node // 2. g[] is adjacency list for tree // 3. freq[] stores frequency of nodes // at particular height, it is maintained // for children of a node int n, k, mx_depth, ans; vector< bool > removed; vector< int > size, freq; vector<vector< int > > g; // Constructor for struct CD( int n1, int k1) { n = n1; k = k1; ans = mx_depth = 0; g.resize(n); size.resize(n); freq.resize(n); removed.assign(n, false ); } // Utility method to add edges to tree void edge( int u, int v) { u--; v--; g[u].push_back(v); g[v].push_back(u); } // Finds size of a subtree, // ignoring removed nodes in the way int get_size( int node, int par) { if (removed[node]) return 0; size[node] = 1; for ( auto nebr : g[node]) { if (nebr != par) { size[node] += get_size(nebr, node); } } return size[node]; } // Calculates centroid of a subtree // of 'node' of size 'sz' int get_centroid( int node, int par, int sz) { for ( auto nebr : g[node]) { if (nebr != par && !removed[nebr] && size[nebr] > sz / 2) { return get_centroid(nebr, node, sz); } } return node; } // Decompose the tree // into various centroids void decompose( int node, int par) { get_size(node, -1); // c is centroid of subtree 'node' int c = get_centroid(node, par, size[node]); // Find paths_originating_from 'c' paths_originating_from(c); // Mark this centroid as removed removed = true ; // Find other centroids for ( auto nebr : g) { if (!removed[nebr]) { decompose(nebr, c); } } } // This dfs is responsible for // calculating ans and // updating freq vector void dfs( int node, int par, int depth, bool contri) { if (depth > k) return ; mx_depth = max(mx_depth, depth); if (contri) { ans += freq[k - depth]; } else { freq[depth]++; } for ( auto nebr : g[node]) { if (nebr != par && !removed[nebr]) { dfs(nebr, node, depth + 1, contri); } } } // Function to find K-length paths // originating from node void paths_originating_from( int node) { mx_depth = 0; freq[0] = 1; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it for ( auto nebr : g[node]) { if (!removed[nebr]) { dfs(nebr, node, 1, true ); dfs(nebr, node, 1, false ); } } // Re-initialize freq vector for ( int i = 0; i <= mx_depth; ++i) { freq[i] = 0; } } }; // Driver code int main() { int N = 5, K = 2; CD cd_s(N, K); cd_s.edge(1, 2); cd_s.edge(1, 5); cd_s.edge(2, 3); cd_s.edge(2, 4); cd_s.decompose(0, -1); cout << cd_s.ans; return 0; } |
Java
// Java Code to implement above approach import java.util.*; public class Solution { // Struct for centroid decomposition static class CD { // 1. mx_depth will be used to store // the height of a node // 2. g[] is adjacency list for tree // 3. freq[] stores frequency of nodes // at particular height, it is maintained // for children of a node int n, k, mx_depth, ans; boolean [] removed; int [] size, freq; ArrayList<ArrayList<Integer> > g; // Constructor for struct CD( int n1, int k1) { n = n1; k = k1; ans = mx_depth = 0 ; g = new ArrayList<>(); for ( int i = 0 ; i < n; i++) { g.add( new ArrayList<>()); } size = new int [n]; freq = new int [n]; removed = new boolean [n]; } // Utility method to add edges to tree public void edge( int u, int v) { u--; v--; g.get(u).add(v); g.get(v).add(u); } // Finds size of a subtree, // ignoring removed nodes in the way public int get_size( int node, int par) { if (removed[node]) return 0 ; size[node] = 1 ; for (Integer nebr : g.get(node)) { if (nebr != par) { size[node] += get_size(nebr, node); } } return size[node]; } // Calculates centroid of a subtree // of 'node' of size 'sz' int get_centroid( int node, int par, int sz) { for (Integer nebr : g.get(node)) { if (nebr != par && !removed[nebr] && size[nebr] > sz / 2 ) { return get_centroid(nebr, node, sz); } } return node; } // Decompose the tree // into various centroids public void decompose( int node, int par) { get_size(node, - 1 ); // c is centroid of subtree 'node' int c = get_centroid(node, par, size[node]); // Find paths_originating_from 'c' paths_originating_from(c); // Mark this centroid as removed removed = true ; // Find other centroids for (Integer nebr : g.get(c)) { if (!removed[nebr]) { decompose(nebr, c); } } } // This dfs is responsible for // calculating ans and // updating freq vector void dfs( int node, int par, int depth, boolean contri) { if (depth > k) return ; mx_depth = Math.max(mx_depth, depth); if (contri) { ans += freq[k - depth]; } else { freq[depth]++; } for (Integer nebr : g.get(node)) { if (nebr != par && !removed[nebr]) { dfs(nebr, node, depth + 1 , contri); } } } // Function to find K-length paths // originating from node void paths_originating_from( int node) { mx_depth = 0 ; freq[ 0 ] = 1 ; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it for (Integer nebr : g.get(node)) { if (!removed[nebr]) { dfs(nebr, node, 1 , true ); dfs(nebr, node, 1 , false ); } } // Re-initialize freq vector for ( int i = 0 ; i <= mx_depth; ++i) { freq[i] = 0 ; } } }; // Driver code public static void main(String[] args) { int N = 5 , K = 2 ; CD cd_s = new CD(N, K); cd_s.edge( 1 , 2 ); cd_s.edge( 1 , 5 ); cd_s.edge( 2 , 3 ); cd_s.edge( 2 , 4 ); cd_s.decompose( 0 , - 1 ); System.out.println(cd_s.ans); } } // This code is contributed by karandeep1234 |
Python3
# Python code to implement above approach # Struct for centroid decomposition class CD: # Constructor for struct def __init__( self , n1, k1): self .n = n1 self .k = k1 self .ans = self .mx_depth = 0 self .removed = [ False ] * n1 self .size = [ 0 ] * n1 self .freq = [ 0 ] * n1 self .g = [[] for i in range (n1)] # Utility method to add edges to tree def edge( self , u, v): u - = 1 v - = 1 self .g[u].append(v) self .g[v].append(u) # Finds size of a subtree, # ignoring removed nodes in the way def get_size( self , node, par): if self .removed[node]: return 0 self .size[node] = 1 for nebr in self .g[node]: if nebr ! = par: self .size[node] + = self .get_size(nebr, node) return self .size[node] # Calculates centroid of a subtree # of 'node' of size 'sz' def get_centroid( self , node, par, sz): for nebr in self .g[node]: if nebr ! = par and not self .removed[nebr] and self .size[nebr] > sz / 2 : return self .get_centroid(nebr, node, sz) return node # Decompose the tree # into various centroids def decompose( self , node, par): self .get_size(node, - 1 ) # c is centroid of subtree 'node' c = self .get_centroid(node, par, self .size[node]) # Find paths_originating_from 'c' self .paths_originating_from(c) # Mark this centroid as removed self .removed = True # Find other centroids for nebr in self .g: if not self .removed[nebr]: self .decompose(nebr, c) # This dfs is responsible for # calculating ans and # updating freq vector def dfs( self , node, par, depth, contri): if depth > self .k: return self .mx_depth = max ( self .mx_depth, depth) if contri: self .ans + = self .freq[ self .k - depth] else : self .freq[depth] + = 1 for nebr in self .g[node]: if nebr ! = par and not self .removed[nebr]: self .dfs(nebr, node, depth + 1 , contri) # Function to find K-length paths # originating from node def paths_originating_from( self , node): self .mx_depth = 0 self .freq[ 0 ] = 1 # For every not-removed nebr, # calculate its contribution, # then update freq vector for it for nebr in self .g[node]: if not self .removed[nebr]: self .dfs(nebr, node, 1 , True ) self .dfs(nebr, node, 1 , False ) # Re-initialize freq vector for i in range ( self .mx_depth + 1 ): self .freq[i] = 0 # Driver code if __name__ = = "__main__" : N = 5 K = 2 cd_s = CD(N, K) cd_s.edge( 1 , 2 ) cd_s.edge( 1 , 5 ) cd_s.edge( 2 , 3 ) cd_s.edge( 2 , 4 ) cd_s.decompose( 0 , - 1 ) print (cd_s.ans) |
Javascript
class CD { constructor(n1, k1) { this .n = n1; this .k = k1; this .ans = this .mx_depth = 0; this .removed = Array(n1).fill( false ); this .size = Array(n1).fill(0); this .freq = Array(n1).fill(0); this .g = Array(n1).fill().map(() => []); } edge(u, v) { u -= 1; v -= 1; this .g[u].push(v); this .g[v].push(u); } get_size(node, par) { if ( this .removed[node]) { return 0; } this .size[node] = 1; for (let nebr of this .g[node]) { if (nebr !== par) { this .size[node] += this .get_size(nebr, node); } } return this .size[node]; } get_centroid(node, par, sz) { for (let nebr of this .g[node]) { if (! this .removed[nebr] && nebr !== par && this .size[nebr] > sz / 2) { return this .get_centroid(nebr, node, sz); } } return node; } decompose(node, par) { this .get_size(node, -1); let c = this .get_centroid(node, par, this .size[node]); this .paths_originating_from(c); this .removed = true ; for (let nebr of this .g) { if (! this .removed[nebr]) { this .decompose(nebr, c); } } } dfs(node, par, depth, contri) { if (depth > this .k) { return ; } this .mx_depth = Math.max( this .mx_depth, depth); if (contri) { this .ans += this .freq[ this .k - depth]; } else { this .freq[depth] += 1; } for (let nebr of this .g[node]) { if (nebr !== par && ! this .removed[nebr]) { this .dfs(nebr, node, depth + 1, contri); } } } paths_originating_from(node) { this .mx_depth = 0; this .freq[0] = 1; for (let nebr of this .g[node]) { if (! this .removed[nebr]) { this .dfs(nebr, node, 1, true ); this .dfs(nebr, node, 1, false ); } } for (let i = 0; i < this .mx_depth + 1; i++) { this .freq[i] = 0; } } } const N = 5; const K = 2; const cd_s = new CD(N, K); cd_s.edge(1, 2); cd_s.edge(1, 5); cd_s.edge(2, 3); cd_s.edge(2, 4); cd_s.decompose(0, -1); console.log(cd_s.ans); |
C#
// C++ code to implement above approach using System; using System.Collections.Generic; class CD { // 1. mx_depth will be used to store // the height of a node // 2. g[] is adjacency list for tree // 3. freq[] stores frequency of nodes // at particular height, it is maintained // for children of a node private int n, k, mx_depth, ans; private List< bool > removed; private List< int > size, freq; private List<List< int > > g; // Constructor for struct public CD( int n1, int k1) { n = n1; k = k1; ans = mx_depth = 0; g = new List<List< int > >(); for ( int i = 0; i < n; i++) g.Add( new List< int >()); size = new List< int >(); size.AddRange( new int [n]); freq = new List< int >(); freq.AddRange( new int [n]); removed = new List< bool >(); removed.AddRange( new bool [n]); } // Utility method to add edges to tree public void Edge( int u, int v) { u--; v--; g[u].Add(v); g[v].Add(u); } // Finds size of a subtree, // ignoring removed nodes in the way private int GetSize( int node, int par) { if (removed[node]) return 0; size[node] = 1; foreach ( int nebr in g[node]) { if (nebr != par) size[node] += GetSize(nebr, node); } return size[node]; } // Calculates centroid of a subtree // of 'node' of size 'sz' private int GetCentroid( int node, int par, int sz) { foreach ( int nebr in g[node]) { if (!removed[nebr] && nebr != par && size[nebr] > sz / 2) return GetCentroid(nebr, node, sz); } return node; } // Decompose the tree // into various centroids private void Decompose( int node, int par) { GetSize(node, -1); // c is centroid of subtree 'node' int c = GetCentroid(node, par, size[node]); // Find paths_originating_from 'c' PathsOriginatingFrom(c); // Mark this centroid as removed removed = true ; // Find other centroids foreach ( int nebr in g) { if (!removed[nebr]) Decompose(nebr, c); } } // This dfs is responsible for // calculating ans and // updating freq vector private void Dfs( int node, int par, int depth, bool contri) { if (depth > k) return ; mx_depth = Math.Max(mx_depth, depth); if (contri) ans += freq[k - depth]; else freq[depth]++; foreach ( int nebr in g[node]) { if (!removed[nebr] && nebr != par) Dfs(nebr, node, depth + 1, contri); } } // Function to find K-length paths // originating from node private void PathsOriginatingFrom( int node) { mx_depth = 0; freq[0] = 1; // For every not-removed nebr, // calculate its contribution, // then update freq vector for it foreach ( int nebr in g[node]) { if (!removed[nebr]) { Dfs(nebr, node, 1, true ); Dfs(nebr, node, 1, false ); } } for ( int i = 0; i <= mx_depth; i++) freq[i] = 0; } public int Solve() { Decompose(0, -1); return ans; } } // Driver code class Program { static void Main( string [] args) { int N = 5, K = 2; CD cd_s = new CD(N, K); cd_s.Edge(1, 2); cd_s.Edge(1, 5); cd_s.Edge(2, 3); cd_s.Edge(2, 4); Console.WriteLine(cd_s.Solve()); } } // This code is generated by Chetan Bargal |
4
Time Complexity: O(N * log(N)) where log N is the height of the tree
Auxiliary Space: O(N)
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!