Dynamic Programming combined with bitmasking allows us to handle problems where states are subsets of elements. Instead of tracking states with arrays, we compress the information into a bitmask integer (binary representation of chosen/un-chosen states).
This technique shines in combinatorial optimization problems like Traveling Salesman, Hamiltonian paths, partitioning, and subset DP.
π Key Concepts
- Bitmask Representation
- Each bit in an integer represents whether an element is included or not.
- Example: For
n = 4
, mask1011
means elements{0,1,3}
are chosen.
- State Definition
-
dp[mask]
: Best answer considering subset represented bymask
. -
dp[pos][mask]
: Best answer ending atpos
with visited nodes inmask
.
- Transitions
- Add/remove an element by flipping a bit.
- Iterate over all bits set/unset in a mask.
π Classic Problems & Patterns
1. Traveling Salesman Problem (TSP)
Find the shortest path visiting all cities exactly once and return to start.
class TSP {
private static final int INF = 1_000_000;
public int tsp(int[][] dist) {
int n = dist.length;
int N = 1 << n;
int[][] dp = new int[n][N];
// Initialize DP
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], INF);
}
dp[0][1] = 0; // start at city 0, visited mask = 000..1
for (int mask = 1; mask < N; mask++) {
for (int u = 0; u < n; u++) {
if ((mask & (1 << u)) == 0) continue; // u not visited
for (int v = 0; v < n; v++) {
if ((mask & (1 << v)) != 0) continue; // v already visited
int nextMask = mask | (1 << v);
dp[v][nextMask] = Math.min(dp[v][nextMask], dp[u][mask] + dist[u][v]);
}
}
}
int ans = INF;
for (int i = 0; i < n; i++) {
ans = Math.min(ans, dp[i][N-1] + dist[i][0]);
}
return ans;
}
}
2. Partition into K Equal Sum Subsets
Check if an array can be partitioned into k
subsets with equal sum.
class PartitionK {
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum = Arrays.stream(nums).sum();
if (sum % k != 0) return false;
int target = sum / k, n = nums.length;
boolean[] dp = new boolean[1 << n];
int[] subsetSum = new int[1 << n];
dp[0] = true;
for (int mask = 0; mask < (1 << n); mask++) {
if (!dp[mask]) continue;
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) continue; // already taken
int nextMask = mask | (1 << i);
if (dp[nextMask]) continue;
if (nums[i] + subsetSum[mask] % target <= target) {
dp[nextMask] = true;
subsetSum[nextMask] = subsetSum[mask] + nums[i];
}
}
}
return dp[(1 << n) - 1];
}
}
3. Counting Hamiltonian Paths
class HamiltonianPaths {
public int countHamiltonianPaths(int[][] graph) {
int n = graph.length;
int[][] dp = new int[1 << n][n];
dp[1][0] = 1; // start at 0
for (int mask = 1; mask < (1 << n); mask++) {
for (int u = 0; u < n; u++) {
if ((mask & (1 << u)) == 0) continue;
for (int v = 0; v < n; v++) {
if ((mask & (1 << v)) != 0) continue;
if (graph[u][v] == 1) {
dp[mask | (1 << v)][v] += dp[mask][u];
}
}
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
ans += dp[(1 << n) - 1][i];
}
return ans;
}
}
4. Minimum XOR Sum of Two Arrays
class MinXORSum {
public int minimumXORSum(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] dp = new int[1 << n];
Arrays.fill(dp, Integer.MAX_VALUE / 2);
dp[0] = 0;
for (int mask = 0; mask < (1 << n); mask++) {
int i = Integer.bitCount(mask); // number of assigned elements
for (int j = 0; j < n; j++) {
if ((mask & (1 << j)) == 0) {
dp[mask | (1 << j)] = Math.min(dp[mask | (1 << j)],
dp[mask] + (nums1[i] ^ nums2[j]));
}
}
}
return dp[(1 << n) - 1];
}
}
β‘ Key Takeaways
- Bitmask DP compresses exponential state space into integers.
- Useful for subset problems, assignment problems, Hamiltonian/TSP problems.
- Works best when
n <= 20
(since2^n
states explode otherwise). - Combine with memoization for recursive style or iterative tabulation.
Top comments (0)