Hamiltonian Path & Backtracking

1. Algorithm & Complexity

A Hamiltonian path in a graph is a simple path that visits every vertex exactly once. To find one (or count all), we can use backtracking (DFS with state‐undoing):

  1. Represent the graph as an adjacency list adj[u].
  2. Keep a boolean array used[v] and a vector path.
  3. Start DFS from each possible start‐vertex s (if you just need one path, you can stop when found):
    void dfs(int u) {
        if (path.size()==N) { /* found a full Hamiltonian path */ }
        for (int v: adj[u]) {
          if (!used[v]) {
            used[v]=true;
            path.push_back(v);
            dfs(v);
            path.pop_back();
            used[v]=false;
          }
        }
    }
  4. For each s=0…N-1, set used[s]=true; path={s}; dfs(s); used[s]=false.

Time Complexity

In the worst case (complete graph), from each vertex you branch into up to (N−1) unused neighbors, then (N−2), etc. Total ≈ O(N!).

Memory Complexity

  • used[0…N−1] → O(N)
  • current path of length ≤ N → O(N)
  • recursion stack depth ≤ N → O(N)
Total: O(N) extra memory.


2. Two Easy Examples

2.1 Codeforces #{{easy1_id}} – Count All Hamiltonian Paths in a Tiny Graph

Given an undirected graph with N≤8 vertices (numbered 1…N) and M edges, count all Hamiltonian paths of length N−1.

Input:
4 5
1 2
2 3
2 4
1 4
3 4

Output:
6
#include <iostream>
#include <vector>
using namespace std;

int N, M, answer = 0;
vector<vector<int>> adj;
vector<bool> used;
vector<int> path;

void dfs(int u) {
    if (path.size() == N) {
        answer++;
        return;
    }
    for (int v : adj[u]) {
        if (!used[v]) {
            used[v] = true;
            path.push_back(v);
            dfs(v);
            path.pop_back();
            used[v] = false;
        }
    }
}

int main() {
    cin >> N >> M;
    adj.assign(N, {});
    used.assign(N, false);
    for (int i = 0; i < M; i++) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    for (int s = 0; s < N; s++) {
        used[s] = true;
        path = {s};
        dfs(s);
        used[s] = false;
    }
    cout << answer << "\n";
    return 0;
}

2.2 Codeforces #{{easy2_id}} – Find Any Hamiltonian Path

Given an undirected graph with N≤10 and M, output any one Hamiltonian path if it exists, or -1 otherwise.

Input:
5 5
1 2
2 3
3 4
4 5
2 5

Output:
1 2 3 4 5
#include <iostream>
#include <vector>
using namespace std;

int N, M;
vector<vector<int>> adj;
vector<bool> used;
vector<int> path;
bool found = false;

void dfs(int u) {
    if (found) return;
    if (path.size() == N) {
        for (int x : path) cout << x+1 << ' ';
        cout << "\n";
        found = true;
        return;
    }
    for (int v : adj[u]) {
        if (!used[v]) {
            used[v] = true;
            path.push_back(v);
            dfs(v);
            if (found) return;
            path.pop_back();
            used[v] = false;
        }
    }
}

int main() {
    cin >> N >> M;
    adj.assign(N, {});
    used.assign(N, false);
    for (int i = 0; i < M; i++) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    for (int s = 0; s < N; s++) {
        if (found) break;
        used[s] = true;
        path = {s};
        dfs(s);
        used[s] = false;
    }
    if (!found) cout << "-1\n";
    return 0;
}

3. Two Intermediate Examples

3.1 Codeforces #{{inter1_id}} – TSP on Small Graph (Min‐Cost Hamiltonian Path)

Given a weighted undirected complete graph with N≤12 and matrix w[i][j], find the minimum‐cost Hamiltonian path from any start to any end.

Input:
4
0 5 3 4
5 0 2 6
3 2 0 7
4 6 7 0

Output:
10
#include <iostream>
#include <vector>
#include <limits>
using namespace std;

int N;
vector<vector<int>> w;
vector<bool> used;
int best = numeric_limits<int>::max();
int currCost = 0;

void dfs(int u, int depth) {
    if (depth == N) {
        best = min(best, currCost);
        return;
    }
    for (int v = 0; v < N; v++) {
        if (!used[v]) {
            used[v] = true;
            currCost += w[u][v];
            dfs(v, depth + 1);
            currCost -= w[u][v];
            used[v] = false;
        }
    }
}

int main() {
    cin >> N;
    w.assign(N, vector<int>(N));
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            cin >> w[i][j];
    used.assign(N, false);
    for (int s = 0; s < N; s++) {
        used[s] = true;
        dfs(s, 1);
        used[s] = false;
    }
    cout << best << "\n";
    return 0;
}

3.2 Codeforces #{{inter2_id}} – Knight’s Tour on Small Board

On an n×n board with n≤6, place a knight so it visits every cell exactly once (a Hamiltonian path on the knight‐move graph). Count the tours.

Input:
5

Output:
304
#include <iostream>
#include <vector>
using namespace std;

int n, total = 0;
vector<vector<bool>> used;
int dr[8] = {-2,-2,-1,-1,1,1,2,2}, dc[8] = {-1,1,-2,2,-2,2,-1,1};

void dfs(int r, int c, int visited) {
    if (visited == n*n) {
        total++;
        return;
    }
    for (int k = 0; k < 8; k++) {
        int nr = r + dr[k], nc = c + dc[k];
        if (nr>=0 && nr=0 && nc> n;
    used.assign(n, vector<bool>(n, false));
    // try all starting cells
    for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++) {
        used[i][j] = true;
        dfs(i, j, 1);
        used[i][j] = false;
    }
    cout << total << "\n";
    return 0;
}

4. One Hard Example

Codeforces #{{hard_id}} – General Hamiltonian Path Existence & Count

Given an undirected graph with N≤15, M≤40, count all Hamiltonian paths. (Brute‐force backtracking is borderline but works with pruning.)

Input:
6 7
1 2
2 3
3 4
4 5
5 6
2 5
3 6

Output:
4
#include <iostream>
#include <vector>>
using namespace std;

int N, M, answer = 0;
vector<vector<int>> adj;
vector<bool> used;
vector<int> path;

void dfs(int u) {
    if (path.size() == N) {
        answer++;
        return;
    }
    for (int v : adj[u]) {
        if (!used[v]) {
            used[v] = true;
            path.push_back(v);
            dfs(v);
            path.pop_back();
            used[v] = false;
        }
    }
}

int main() {
    cin >> N >> M;
    adj.assign(N, {});
    used.assign(N, false);
    for (int i = 0; i < M; i++) {
        int u, v;
        cin >> u >> v;
        --u; --v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    for (int s = 0; s < N; s++) {
        used[s] = true;
        path = {s};
        dfs(s);
        used[s] = false;
    }
    cout << answer << "\n";
    return 0;
}

All these examples share the same backtracking blueprint:

  • Choose a next vertex → recurse → undo choice → try next.
  • Maintain used[] for O(1) conflict checks.