Counting roundtrip paths in a graph given a number of steps
Problem
This problem is asked at so: I have a network of stations in a subway system. The number of stations, the number of tickets I can travel between stations with, and which stations are connected to each other are given in a text file as input to the program. Which stations are connected to each other are kept in a 2D boolean matrix. I have to find the number of paths from station 0 and back to 0 that uses all of the tickets.
OR
Given a graph (as an adjacency matrix), count the number of roundtrip paths of exactly k steps that start and end at the origin node (node 0).
Examples
Example 1
graph TD; A(0) --- B(1) & E(4) B --- F(5) & C(2) & G(6) F --- D(3) C --- D G --- D E --- D
Adjacency matrix of the graph:
Input:
adj = [
[0, 1, 0, 0, 1, 0, 0],
[1, 0, 1, 0, 0, 1, 1],
[0, 1, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 1, 1, 1],
[1, 0, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0]
],
k = 5
Output: 6
Explanation: Starting and returning to 0, there are 6 roundtrip paths of length 5:
0-1-2-3-4-0
0-1-5-3-4-0
0-1-6-3-4-0
0-4-3-6-1-0
0-4-3-5-1-0
0-4-3-2-1-0
Solution
Method 1 – Top-Down Dynamic Programming (Memoized Recursion)
Intuition
Use recursion with memoization to count the number of ways to return to node 0 in exactly k steps. At each step, try all possible next nodes, and cache results to avoid recomputation.
Approach
- Define a function
f(u, t)= number of ways to reach node 0 from node u in t steps. - Base case: if
t == 0, return 1 ifu == 0, else 0. - For each neighbor v of u, recursively compute
f(v, t-1)and sum the results. - Use memoization to cache results for
(u, t). - The answer is
f(0, k).
Code
C++
#include <vector>
#include <cstring>
using namespace std;
class Solution {
public:
int countRoundtripPaths(const vector<vector<int>>& adj, int k) {
int n = adj.size();
vector<vector<int>> memo(n, vector<int>(k+1, -1));
function<int(int,int)> dp = [&](int u, int t) {
if (t == 0) return u == 0 ? 1 : 0;
if (memo[u][t] != -1) return memo[u][t];
int res = 0;
for (int v = 0; v < n; ++v) if (adj[u][v]) res += dp(v, t-1);
return memo[u][t] = res;
};
return dp(0, k);
}
};
Java
import java.util.*;
class Solution {
public int countRoundtripPaths(int[][] adj, int k) {
int n = adj.length;
Integer[][] memo = new Integer[n][k+1];
return dp(0, k, adj, memo);
}
private int dp(int u, int t, int[][] adj, Integer[][] memo) {
if (t == 0) return u == 0 ? 1 : 0;
if (memo[u][t] != null) return memo[u][t];
int res = 0;
for (int v = 0; v < adj.length; ++v) if (adj[u][v] == 1) res += dp(v, t-1, adj, memo);
return memo[u][t] = res;
}
}
Python
class Solution:
def count_roundtrip_paths(self, adj: list[list[int]], k: int) -> int:
from functools import lru_cache
n = len(adj)
@lru_cache(maxsize=None)
def dp(u, t):
if t == 0:
return 1 if u == 0 else 0
res = 0
for v in range(n):
if adj[u][v]:
res += dp(v, t-1)
return res
return dp(0, k)
Complexity
- ⏰ Time complexity:
O(kN^2), where k is the number of steps and N is the number of nodes. Each state(u, t)is computed once, and for each, we try allNneighbors. - 🧺 Space complexity:
O(kN), for the memoization table.
Method 2 – Bottom-Up Dynamic Programming
Intuition
Use dynamic programming to count the number of ways to reach each node in exactly t steps, building up from 0 to k steps. The answer is the number of ways to return to the origin node in k steps.
Approach
- Let
dp[t][u]be the number of ways to reach node u in t steps. - Initialize
dp[0][0] = 1(start at node 0 with 0 steps). - For each step t from 1 to k:
- For each node s, if
dp[t-1][s] > 0, for each neighbor adj of s:- Add
dp[t-1][s]todp[t][adj]if there is an edge from s to adj.
- Add
- The answer is
dp[k][0](number of ways to return to node 0 in k steps).
Code
C++
class Solution {
public:
int countRoundtripPaths(const vector<vector<int>>& adj, int k) {
int n = adj.size();
vector<vector<int>> dp(k + 1, vector<int>(n, 0));
dp[0][0] = 1;
for (int t = 1; t <= k; ++t) {
for (int s = 0; s < n; ++s) {
if (dp[t-1][s] == 0) continue;
for (int adjNode = 0; adjNode < n; ++adjNode) {
if (adj[s][adjNode]) {
dp[t][adjNode] += dp[t-1][s];
}
}
}
}
return dp[k][0];
}
};
Java
class Solution {
public int countRoundtripPaths(int[][] adj, int k) {
int n = adj.length;
int[][] dp = new int[k + 1][n];
dp[0][0] = 1;
for (int t = 1; t <= k; t++) {
for (int s = 0; s < n; s++) {
if (dp[t-1][s] == 0) continue;
for (int adjNode = 0; adjNode < n; adjNode++) {
if (adj[s][adjNode] == 1) {
dp[t][adjNode] += dp[t-1][s];
}
}
}
}
return dp[k][0];
}
}
Python
class Solution:
def count_roundtrip_paths(self, adj: list[list[int]], k: int) -> int:
n = len(adj)
dp = [[0] * n for _ in range(k + 1)]
dp[0][0] = 1
for t in range(1, k + 1):
for s in range(n):
if dp[t-1][s] == 0:
continue
for adj_node in range(n):
if adj[s][adj_node]:
dp[t][adj_node] += dp[t-1][s]
return dp[k][0]
Complexity
- ⏰ Time complexity:
O(kN^2), where k is the number of steps and N is the number of nodes. Each step considers all pairs of nodes. - 🧺 Space complexity:
O(kN), for the DP table.