Approach: use DFS/backtracking with a bitmask for visited nodes (int since n <= 15). Start DFS from each vertex to enumerate all Hamiltonian paths. Prune using:- neighbor ordering: try neighbors with smaller remaining degree first (Warnsdorff-like) to reduce branching.- early disconnected-component check: if unvisited vertices are not all reachable from the current vertex via remaining edges, prune.- bitmask visited for O(1) checks and compact state.java
import java.util.*;
public class Hamiltonian {
// n <= 15
public static List<List<Integer>> hamiltonianPaths(int n, List<List<Integer>> adj) {
List<List<Integer>> result = new ArrayList<>();
int ALL_VISITED = (1 << n) - 1;
int[] degree = new int[n];
for (int i = 0; i < n; i++) degree[i] = adj.get(i).size();
for (int start = 0; start < n; start++) {
LinkedList<Integer> path = new LinkedList<>();
path.add(start);
dfs(start, 1 << start, path, result, adj, degree, ALL_VISITED);
}
return result;
}
private static void dfs(int u, int visited, LinkedList<Integer> path, List<List<Integer>> adj,
int[] degree, int ALL_VISITED) {
if (visited == ALL_VISITED) {
resultAdd(path, adj); // will be defined below via static capture
return;
}
// Connectivity prune: quick check that all unvisited nodes are reachable from u in induced subgraph
if (!remainingConnected(u, visited, adj, Integer.bitCount(ALL_VISITED ^ visited))) return;
// Order neighbors by heuristic: fewest free-neighbors first (better branching)
List<Integer> cand = new ArrayList<>();
for (int v : adj.get(u)) if ((visited & (1 << v)) == 0) cand.add(v);
cand.sort(Comparator.comparingInt(a -> freeNeighborCount(a, visited, adj)));
for (int v : cand) {
path.add(v);
dfs(v, visited | (1 << v), path, adj, degree, ALL_VISITED);
path.removeLast();
}
}
// count unvisited neighbors of x
private static int freeNeighborCount(int x, int visited, List<List<Integer>> adj) {
int cnt = 0;
for (int w : adj.get(x)) if ((visited & (1 << w)) == 0) cnt++;
return cnt;
}
// check connectivity of remaining nodes (including current u) using BFS on induced subgraph
private static boolean remainingConnected(int u, int visited, List<List<Integer>> adj, int remCount) {
int maskRem = (~visited);
int start = u;
boolean[] seen = new boolean[adj.size()];
Deque<Integer> dq = new ArrayDeque<>();
dq.add(start);
seen[start] = true;
int seenCount = ((visited & (1 << start)) == 0) ? 1 : 0; // if start was unvisited (rare)
while (!dq.isEmpty()) {
int x = dq.poll();
for (int y : adj.get(x)) {
if ((maskRem & (1 << y)) != 0 && !seen[y]) {
seen[y] = true;
seenCount++;
dq.add(y);
}
}
}
// Note: we must ensure all remCount nodes are reachable; seenCount counts only unvisited reached
return seenCount == remCount;
}
// helper to add snapshot to result (to avoid exposing static fields)
private static final List<List<Integer>> resultCollector = new ArrayList<>();
private static void resultAdd(LinkedList<Integer> path, List<List<Integer>> adj) {
resultCollector.add(new ArrayList<>(path));
}
}
Key points:- Bitmask gives O(1) visited checks and compact recursion state.- Ordering neighbors by fewest free neighbors tends to reduce branching (heuristic from knight-tour).- Connectivity check prunes branches that cannot cover all remaining nodes.Time/space:- Worst-case exponential O(n!) paths but effective pruning usually reduces search for small n (n <= 15).Edge cases:- Disconnected input graph (no paths).- Graphs with isolated vertices.Alternatives:- DP over subsets (Held-Karp) can count Hamiltonian paths but doesn't enumerate easily.- Use memoization to detect identical visited states reached at same last vertex to avoid recomputation (trade memory for speed).