// Hamiltonian Path Problem
// Given an adjacency matrix adj of a graph, determine whether the graph has a Hamiltonian path or not.
// time complexity: O(2^N * N^2)
// space complexity: O(N * 2^N)
 
function Hamiltonian_path(adj)
{
	const N = adj.length; // Number of nodes in the graph
	
	// intialize dp (Dynamic Programming) array N*2^N with 0.
	// dp[i][j]=1 if there's a path visits all nodes in the subset j and ends at i
	var dp = Array.from(Array(N), ()=> Array(1 << N).fill(0)); // space: O(N * 2^N)
  
	// Set all dp[i][(1 << i)] (=dp[i][2^i]) to 1
	for (var i = 0; i < N; i++) dp[i][(1 << i)] = true;
 
	// Iterate over each subset i of nodes
	for (var i = 0; i < (1 << N); i++) { // time: O(2^N)
		// Iterate over each node j
		for (var j = 0; j < N; j++) { // time: O(N)
    
			// If the node j is included in the current subset i
			if (i & (1 << j)) {
				// Iterate over each node k
				for (var k = 0; k < N; k++) { // time: O(N)
				
					if (i & (1 << k) // if k is in the subset i
						&& adj[k][j] // and there's an edge between k and j
						&& j != k // and j!=k
						&& dp[k][i ^ (1 << j)]) { // and there's a valid HP ending at k with the subset i excluding j (dp[k][i ^ (1 << j)]).
						
						dp[j][i] = true; // Update dp[j][i] to true
						break;
					}
				}
			}
		}
	}
 
	// for each node i
	for (var i = 0; i < N; i++) {
		if (dp[i][(1 << N) - 1]) // if there's a HP ending at i with all nodes visited (subset 2^N-1 is the set of all nodes)
			return true;
	}
 
	// Otherwise, return false
	return false;
}