Pattern Guide
Meet in the Middle
"Split problem in half. Enumerate each half independently. Combine in O(n log n)."
Meet in the middle (MITM) reduces O(2^n) to O(2^(n/2)) by splitting the problem into two halves, enumerating all possibilities for each half, sorting one half, then binary-searching for complements. Solves subset sum, 4-sum, birthday paradox exploits, and cryptographic meet-in-the-middle attacks. Essential when n ≈ 40 (too large for brute force, too small for DP).
Problems you can solve with this pattern
4 problems · click any to start solving
// Count subsets with sum = target
// n up to 40: too slow for 2^40, but 2*2^20 is fine
function countSubsets(nums, target) {
const n = nums.length;
const half = n >> 1;
const left = nums.slice(0, half);
const right = nums.slice(half);
// Enumerate all subset sums for left half
const leftSums = [];
for (let mask = 0; mask < (1 << left.length); mask++) {
let sum = 0;
for (let i = 0; i < left.length; i++) if ((mask >> i) & 1) sum += left[i];
leftSums.push(sum);
}
leftSums.sort((a, b) => a - b);
// For each right half subset, binary search for complement in left
let count = 0;
for (let mask = 0; mask < (1 << right.length); mask++) {
let sum = 0;
for (let i = 0; i < right.length; i++) if ((mask >> i) & 1) sum += right[i];
const need = target - sum;
// Count leftSums entries equal to need
const lo = lowerBound(leftSums, need);
const hi = upperBound(leftSums, need);
count += hi - lo;
}
return count;
}When n ≈ 40, brute-force 2^40 is too slow but DP is hard or doesn't apply. Meet in the middle: split into two halves of ~20 elements each. Enumerate all 2^20 subsets of each half, generating their sums/states. Sort one half's results. For each element in the other half's results, binary search for the complement. Total: O(2^(n/2) · n) instead of O(2^n).
// Count subsets with sum = target
// n up to 40: too slow for 2^40, but 2*2^20 is fine
function countSubsets(nums, target) {
const n = nums.length;
const half = n >> 1;
const left = nums.slice(0, half);
const right = nums.slice(half);
// Enumerate all subset sums for left half
const leftSums = [];
for (let mask = 0; mask < (1 << left.length); mask++) {
let sum = 0;
for (let i = 0; i < left.length; i++) if ((mask >> i) & 1) sum += left[i];
leftSums.push(sum);
}
leftSums.sort((a, b) => a - b);
// For each right half subset, binary search for complement in left
let count = 0;
for (let mask = 0; mask < (1 << right.length); mask++) {
let sum = 0;
for (let i = 0; i < right.length; i++) if ((mask >> i) & 1) sum += right[i];
const need = target - sum;
// Count leftSums entries equal to need
const lo = lowerBound(leftSums, need);
const hi = upperBound(leftSums, need);
count += hi - lo;
}
return count;
}- n ≈ 40 (2^40 too slow, 2^20 fine)
- Subset sum, counting subsets with property
- Optimization over all subsets
Template:
1. Split input in half
2. Enumerate all 2^(n/2) subsets of each half
3. Sort one half's results
4. For each result in other half: binary search for complement
Complexity: O(2^(n/2) · n) instead of O(2^n)
Grouping trick: For problems needing "take exactly k elements," group subset sums by count k. Then for each k in left half, look at right half sums[target_count - k] — avoids invalid pairings.