Split search space into two halves, enumerate all possibilities in each half, then combine results
Reduce exponential O(2^n) to O(2^(n/2))
O(2^(n/2))
Two Array Sum
Generate all sums from first half, search for complement in second half sums
Find pair from two arrays with target sum
O(2^(n/2) log 2^(n/2))
Hash Map Approach
Store all sums from one half in hash map, check complements from other half
Fast lookup for combination existence
O(2^(n/2))
Two Pointers
Sort both halves' sums, use two pointers from opposite ends
Find all pairs or closest pair to target
O(2^(n/2) log 2^(n/2))
Example: Find if subset sum equals target using two arrays
function canPartitionToTarget(arr, target) { const n = arr.length; const mid = Math.floor(n / 2); // Generate all subset sums for first half function generateSums(start, end) { const sums = []; const size = end - start; for (let mask = 0; mask < (1 << size); mask++) { let sum = 0; for (let i = 0; i < size; i++) { if (mask & (1 << i)) { sum += arr[start + i]; } } sums.push(sum); } return sums; } const leftSums = generateSums(0, mid); const rightSums = generateSums(mid, n); // Sort right sums for binary search rightSums.sort((a, b) => a - b); // For each left sum, check if complement exists in right for (const leftSum of leftSums) { const needed = target - leftSum; // Binary search for needed value let left = 0, right = rightSums.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (rightSums[mid] === needed) { return true; } else if (rightSums[mid] < needed) { left = mid + 1; } else { right = mid - 1; } } } return false;}console.log(canPartitionToTarget([1, 3, 5, 7, 9, 11], 15)); // trueconsole.log(canPartitionToTarget([1, 3, 5, 7, 9, 11], 100)); // false
2. Subset Sum Split Search
Pattern
Technique
Application
Optimization
Split Into Halves
Divide array into two equal/near-equal parts
Reduce search space from O(2^n) to O(2^(n/2))
Significant for n > 20
Generate All Subsets
Use bit manipulation to enumerate all 2^(n/2) subsets
Each subset represents a possible sum
Store in array or set
Combine Results
For each subset sum from first half, search complement in second half
Use binary search or hash set for O(log n) or O(1) lookup
Sort one half for binary search
Count Solutions
Use hash map to count frequency of each sum
Count number of ways to achieve target sum
Multiply frequencies from both halves
Example: Count number of subsets with given sum
function countSubsetsWithSum(arr, target) { const n = arr.length; const mid = Math.floor(n / 2); // Generate subset sums with frequencies function generateSumFrequency(start, end) { const freqMap = new Map(); const size = end - start; for (let mask = 0; mask < (1 << size); mask++) { let sum = 0; for (let i = 0; i < size; i++) { if (mask & (1 << i)) { sum += arr[start + i]; } } freqMap.set(sum, (freqMap.get(sum) || 0) + 1); } return freqMap; } const leftSums = generateSumFrequency(0, mid); const rightSums = generateSumFrequency(mid, n); let count = 0; // For each left sum, find matching right sum for (const [leftSum, leftFreq] of leftSums) { const needed = target - leftSum; if (rightSums.has(needed)) { count += leftFreq * rightSums.get(needed); } } return count;}console.log(countSubsetsWithSum([1, 2, 3, 4, 5], 7)); // Output: 3 (subsets: {2,5}, {3,4}, {1,2,4})
3. Closest Subset Sum
Problem Type
Strategy
Data Structure
Time Complexity
Closest to Target
Generate all sums from both halves, find pair with minimum difference from target
Sorted arrays + two pointers
O(2^(n/2) log 2^(n/2))
Two Pointer Scan
Sort both sum arrays, use pointers from opposite ends to minimize |left + right - target|
Two sorted arrays
O(2^(n/2))
Binary Search
For each left sum, binary search for closest right sum to make target
One sorted array
O(2^(n/2) log 2^(n/2))
Track Min Diff
Maintain global minimum difference while scanning combinations
Variable for best result
Same as search
Example: Find subset sum closest to target
function closestSubsetSum(arr, target) { const n = arr.length; const mid = Math.floor(n / 2); function generateSums(start, end) { const sums = []; const size = end - start; for (let mask = 0; mask < (1 << size); mask++) { let sum = 0; for (let i = 0; i < size; i++) { if (mask & (1 << i)) { sum += arr[start + i]; } } sums.push(sum); } return sums; } const leftSums = generateSums(0, mid); const rightSums = generateSums(mid, n); // Sort both arrays leftSums.sort((a, b) => a - b); rightSums.sort((a, b) => a - b); let closest = Infinity; let left = 0, right = rightSums.length - 1; // Two pointer approach to find closest sum while (left < leftSums.length && right >= 0) { const currentSum = leftSums[left] + rightSums[right]; // Update closest if better if (Math.abs(currentSum - target) < Math.abs(closest - target)) { closest = currentSum; } // Move pointers based on comparison if (currentSum < target) { left++; } else if (currentSum > target) { right--; } else { return currentSum; // Exact match } } return closest;}console.log(closestSubsetSum([1, 3, 5, 7, 9], 15)); // 15console.log(closestSubsetSum([1, 3, 5, 7, 9], 16)); // 15 or 16
Note: Two-pointer approach after sorting both halves is more efficient than nested iteration. The key insight: if current sum is less than target, increase left pointer; if greater, decrease right pointer.
4. 4Sum Problem Optimization
Approach
Method
Advantage
Complexity
Brute Force 4Sum
Four nested loops to check all quadruplets
Simple to implement
O(n^4)
Hash Map 4Sum
Store all pair sums in hash map, look for complement pairs
Reduces to two-pair problem
O(n^2) avg
Meet in Middle
Generate all pair sums from first half and second half, find matching pairs
More memory efficient than hash map approach
O(n^2 log n)
Two Pointer 4Sum
Fix two elements, use two pointers for remaining two
No extra space, handles duplicates well
O(n^3)
Example: 4Sum using Meet in the Middle
function fourSum(nums, target) { const n = nums.length; if (n < 4) return []; nums.sort((a, b) => a - b); const result = []; // Generate all pair sums with indices const pairSums = []; for (let i = 0; i < n - 1; i++) { for (let j = i + 1; j < n; j++) { pairSums.push({ sum: nums[i] + nums[j], i: i, j: j }); } } // Sort by sum pairSums.sort((a, b) => a.sum - b.sum); // Two pointer to find matching pairs let left = 0, right = pairSums.length - 1; while (left < right) { const currentSum = pairSums[left].sum + pairSums[right].sum; if (currentSum === target) { // Check non-overlapping indices const p1 = pairSums[left]; const p2 = pairSums[right]; if (p1.j < p2.i || p2.j < p1.i) { const quad = [ nums[p1.i], nums[p1.j], nums[p2.i], nums[p2.j] ].sort((a, b) => a - b); // Check for duplicates const key = quad.join(','); if (!result.find(r => r.join(',') === key)) { result.push(quad); } } left++; right--; } else if (currentSum < target) { left++; } else { right--; } } return result;}
Example: 4Sum using Two Pointers (Optimal)
function fourSumTwoPointer(nums, target) { nums.sort((a, b) => a - b); const n = nums.length; const result = []; for (let i = 0; i < n - 3; i++) { // Skip duplicates for i if (i > 0 && nums[i] === nums[i-1]) { continue; } for (let j = i + 1; j < n - 2; j++) { // Skip duplicates for j if (j > i + 1 && nums[j] === nums[j-1]) { continue; } let left = j + 1, right = n - 1; while (left < right) { const sum = nums[i] + nums[j] + nums[left] + nums[right]; if (sum === target) { result.push([ nums[i], nums[j], nums[left], nums[right] ]); // Skip duplicates while (left < right && nums[left] === nums[left+1]) { left++; } while (left < right && nums[right] === nums[right-1]) { right--; } left++; right--; } else if (sum < target) { left++; } else { right--; } } } } return result;}console.log(fourSumTwoPointer( [1,0,-1,0,-2,2], 0));// [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
5. Exponential to Polynomial Reduction
Problem Class
Original Complexity
After Meet in Middle
Key Insight
Subset Sum
O(2^n) brute force
O(2^(n/2) × log 2^(n/2))
Split into two halves, combine with sorting/hashing
K-Sum Problem
O(n^k) nested loops
O(n^(k/2) × log n)
Split k elements into two groups of k/2
Knapsack Variants
O(2^n) enumerate all subsets
O(2^(n/2))
Generate half solutions, combine optimally
Partition Problem
O(2^n) all partitions
O(2^(n/2))
Two halves generate sums, check if complement exists
Example: Partition array into two subsets with minimum difference
function minimizeSubsetSumDifference(nums) { const n = nums.length; const totalSum = nums.reduce((a, b) => a + b, 0); const mid = Math.floor(n / 2); // Generate all subset sums from each half function generateSums(start, end) { const sums = []; const size = end - start; for (let mask = 0; mask < (1 << size); mask++) { let sum = 0; for (let i = 0; i < size; i++) { if (mask & (1 << i)) { sum += nums[start + i]; } } sums.push(sum); } return sums; } const leftSums = generateSums(0, mid); const rightSums = generateSums(mid, n); // Sort right sums for binary search rightSums.sort((a, b) => a - b); let minDiff = Infinity; // For each left sum, find best right sum for (const leftSum of leftSums) { // We want leftSum + rightSum to be close to totalSum/2 const target = Math.floor(totalSum / 2) - leftSum; // Binary search for closest value let left = 0, right = rightSums.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); const subset1Sum = leftSum + rightSums[mid]; const subset2Sum = totalSum - subset1Sum; const diff = Math.abs(subset1Sum - subset2Sum); minDiff = Math.min(minDiff, diff); if (rightSums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } // Also check left boundary if (left < rightSums.length) { const subset1Sum = leftSum + rightSums[left]; const subset2Sum = totalSum - subset1Sum; minDiff = Math.min(minDiff, Math.abs(subset1Sum - subset2Sum)); } } return minDiff;}console.log(minimizeSubsetSumDifference([1, 6, 11, 5])); // 1// Explanation: {1,5,6} = 12, {11} = 11, diff = 1
Note: Meet in the middle is most effective when:
Problem has exponential complexity O(2^n) or O(n^k) where k is large
Search space can be divided into independent halves
Results from halves can be combined efficiently (sorting, hashing)
n is typically between 20-40 (too small: regular approach works; too large: even 2^(n/2) is too big)
Example: Comparison of complexities
// Complexity comparison for n = 40:// Brute force: O(2^40) = 1,099,511,627,776 operations (~1 trillion)// Meet in middle: O(2^20 × log 2^20) = 1,048,576 × 20 = 20,971,520 operations (~21 million)// Reduction factor: 2^40 / (2^20 × 20) ≈ 52,428 times faster!// Space complexity:// Brute force: O(1) (no extra space)// Meet in middle: O(2^(n/2)) to store subset sums// Trade-off: Exchange space for dramatic time improvementfunction demonstrateReduction() { const n = 40; console.log("Array size:", n); console.log("Brute force operations:", Math.pow(2, n)); console.log("Meet in middle operations:", Math.pow(2, n/2) * (n/2)); console.log("Speed up factor:", Math.pow(2, n) / (Math.pow(2, n/2) * (n/2))); // For practical purposes: // Brute force: days/weeks of computation // Meet in middle: seconds}
Summary: Meet in the Middle Pattern
Core Principle: Split search space in half, enumerate all possibilities in each half (O(2^(n/2)) each), combine results efficiently
Complexity Reduction: O(2^n) → O(2^(n/2)), dramatic improvement for n > 20
Two Array Sum: Generate sums from both halves, use hash map or binary search to find complementary sums
Subset Sum: Enumerate all 2^(n/2) subset sums from each half, combine with sorting + two pointers or hash map
Closest Sum: Sort both halves' sums, use two pointers to minimize |leftSum + rightSum - target|
4Sum Optimization: Generate all n^2 pair sums, find matching pairs using meet in middle or two pointers
Space-Time Trade-off: Requires O(2^(n/2)) extra space to store subset sums, but achieves exponential time savings
Typical Range: Most effective for 20 ≤ n ≤ 40 where brute force is impossible but 2^(n/2) is manageable