Home/Learn/Meet in the Middle

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).

14 min readdp problems →

Problems you can solve with this pattern

4 problems · click any to start solving

All dp
1Partition Array Into Two Arrays to Minimize Sum DifferenceHardSolve
2Four Sum IIMediumSolve
3Closest Subsequence SumHardSolve
4Shopping OffersMediumSolve
Meet in the middle template — subset sum
// 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).

Meet in the middle template — subset sum
// 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 to use MITM:
- 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.