🧠
Backtracking with Bitmask and Memoization
💡 Using bitmask to represent used matchsticks and memoizing states avoids recomputing the same subproblems, improving efficiency.
Intuition
Represent the set of used matchsticks as a bitmask and store results for each state to avoid repeated work.
Algorithm
- Calculate total length and check divisibility by 4.
- Sort matchsticks descending for pruning.
- Use a recursive function with parameters: current bitmask of used sticks and current side length.
- Try to place unused matchsticks in current side without exceeding target.
- Memoize results for each bitmask to avoid recomputation.
- Return true if all matchsticks are used and sides are formed.
💡 Bitmasking compresses the state space, and memoization prevents repeated exploration of identical states.
from typing import List
def makesquare(matchsticks: List[int]) -> bool:
total = sum(matchsticks)
if total % 4 != 0:
return False
side = total // 4
matchsticks.sort(reverse=True)
n = len(matchsticks)
memo = {}
def backtrack(used_mask, curr_sum, sides_formed):
if sides_formed == 3:
return True
if (used_mask, curr_sum, sides_formed) in memo:
return memo[(used_mask, curr_sum, sides_formed)]
for i in range(n):
if not (used_mask & (1 << i)):
next_sum = curr_sum + matchsticks[i]
if next_sum <= side:
next_mask = used_mask | (1 << i)
if next_sum == side:
if backtrack(next_mask, 0, sides_formed + 1):
memo[(used_mask, curr_sum, sides_formed)] = True
return True
else:
if backtrack(next_mask, next_sum, sides_formed):
memo[(used_mask, curr_sum, sides_formed)] = True
return True
memo[(used_mask, curr_sum, sides_formed)] = False
return False
return backtrack(0, 0, 0)
# Driver code
if __name__ == '__main__':
print(makesquare([1,1,2,2,2])) # True
print(makesquare([3,3,3,3,4])) # False
Line Notes
memo = {}Memo dictionary to store results of states to avoid recomputation
def backtrack(used_mask, curr_sum, sides_formed):Recursive function with bitmask state and current side sum
if sides_formed == 3:If 3 sides formed, last side must be valid, return True
if (used_mask, curr_sum, sides_formed) in memo:Return memoized result if state seen before
if not (used_mask & (1 << i))Check if matchstick i is unused
next_mask = used_mask | (1 << i)Mark matchstick i as used in bitmask
if next_sum == side:If current side completed, move to next side
memo[(used_mask, curr_sum, sides_formed)] = FalseMemoize failure for current state
import java.util.*;
public class MatchsticksToSquare {
private Map<String, Boolean> memo = new HashMap<>();
private int[] matchsticks;
private int side;
private int n;
public boolean makesquare(int[] matchsticks) {
this.matchsticks = matchsticks;
int total = 0;
for (int m : matchsticks) total += m;
if (total % 4 != 0) return false;
side = total / 4;
n = matchsticks.length;
Arrays.sort(matchsticks);
reverse(matchsticks);
return backtrack(0, 0, 0);
}
private void reverse(int[] arr) {
int i = 0, j = arr.length - 1;
while (i < j) {
int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
i++; j--;
}
}
private boolean backtrack(int usedMask, int currSum, int sidesFormed) {
if (sidesFormed == 3) return true;
String key = usedMask + "," + currSum + "," + sidesFormed;
if (memo.containsKey(key)) return memo.get(key);
for (int i = 0; i < n; i++) {
if ((usedMask & (1 << i)) == 0) {
int nextSum = currSum + matchsticks[i];
if (nextSum <= side) {
int nextMask = usedMask | (1 << i);
if (nextSum == side) {
if (backtrack(nextMask, 0, sidesFormed + 1)) {
memo.put(key, true);
return true;
}
} else {
if (backtrack(nextMask, nextSum, sidesFormed)) {
memo.put(key, true);
return true;
}
}
}
}
}
memo.put(key, false);
return false;
}
public static void main(String[] args) {
MatchsticksToSquare solver = new MatchsticksToSquare();
System.out.println(solver.makesquare(new int[]{1,1,2,2,2})); // true
System.out.println(solver.makesquare(new int[]{3,3,3,3,4})); // false
}
}
Line Notes
private Map<String, Boolean> memo = new HashMap<>();Memo map to store results of states
String key = usedMask + "," + currSum + "," + sidesFormed;Create unique key for memoization
if ((usedMask & (1 << i)) == 0)Check if matchstick i is unused
int nextMask = usedMask | (1 << i);Mark matchstick i as used
if (nextSum == side)If current side completed, move to next side
memo.put(key, true);Memoize success for current state
memo.put(key, false);Memoize failure for current state
#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
using namespace std;
class Solution {
public:
int side, n;
vector<int> matchsticks;
unordered_map<int, bool> memo;
bool makesquare(vector<int>& ms) {
matchsticks = ms;
int total = 0;
for (int m : matchsticks) total += m;
if (total % 4 != 0) return false;
side = total / 4;
n = matchsticks.size();
sort(matchsticks.rbegin(), matchsticks.rend());
return backtrack(0, 0, 0, 0);
}
private:
bool backtrack(int usedMask, int currSum, int sidesFormed, int depth) {
if (sidesFormed == 3) return true;
int key = (usedMask << 8) | currSum; // Shift by 8 bits to combine uniquely
if (memo.count(key)) return memo[key];
for (int i = 0; i < n; i++) {
if ((usedMask & (1 << i)) == 0) {
int nextSum = currSum + matchsticks[i];
if (nextSum <= side) {
int nextMask = usedMask | (1 << i);
if (nextSum == side) {
if (backtrack(nextMask, 0, sidesFormed + 1, depth + 1)) {
memo[key] = true;
return true;
}
} else {
if (backtrack(nextMask, nextSum, sidesFormed, depth + 1)) {
memo[key] = true;
return true;
}
}
}
}
}
memo[key] = false;
return false;
}
};
int main() {
Solution sol;
vector<int> test1 = {1,1,2,2,2};
vector<int> test2 = {3,3,3,3,4};
cout << boolalpha << sol.makesquare(test1) << "\n"; // true
cout << boolalpha << sol.makesquare(test2) << "\n"; // false
return 0;
}
Line Notes
unordered_map<int, bool> memo;Memo map to store results of states
int key = (usedMask << 8) | currSum;Create unique key combining usedMask and currSum with shift and OR
if ((usedMask & (1 << i)) == 0)Check if matchstick i is unused
int nextMask = usedMask | (1 << i);Mark matchstick i as used
if (nextSum == side)If current side completed, move to next side
memo[key] = true;Memoize success for current state
memo[key] = false;Memoize failure for current state
function makesquare(matchsticks) {
const total = matchsticks.reduce((a,b) => a+b, 0);
if (total % 4 !== 0) return false;
const side = total / 4;
matchsticks.sort((a,b) => b - a);
const n = matchsticks.length;
const memo = new Map();
function backtrack(usedMask, currSum, sidesFormed) {
if (sidesFormed === 3) return true;
const key = usedMask + ',' + currSum + ',' + sidesFormed;
if (memo.has(key)) return memo.get(key);
for (let i = 0; i < n; i++) {
if ((usedMask & (1 << i)) === 0) {
const nextSum = currSum + matchsticks[i];
if (nextSum <= side) {
const nextMask = usedMask | (1 << i);
if (nextSum === side) {
if (backtrack(nextMask, 0, sidesFormed + 1)) {
memo.set(key, true);
return true;
}
} else {
if (backtrack(nextMask, nextSum, sidesFormed)) {
memo.set(key, true);
return true;
}
}
}
}
}
memo.set(key, false);
return false;
}
return backtrack(0, 0, 0);
}
// Test cases
console.log(makesquare([1,1,2,2,2])); // true
console.log(makesquare([3,3,3,3,4])); // false
Line Notes
const memo = new Map();Memo map to store results of states
const key = usedMask + ',' + currSum + ',' + sidesFormed;Create unique key for memoization
if ((usedMask & (1 << i)) === 0)Check if matchstick i is unused
const nextMask = usedMask | (1 << i);Mark matchstick i as used
if (nextSum === side)If current side completed, move to next side
memo.set(key, true);Memoize success for current state
memo.set(key, false);Memoize failure for current state
TimeO(n * 2^n)
SpaceO(2^n)
Memoization reduces repeated states, but worst case explores subsets represented by bitmask.
💡 For n=15, 2^15=32768 states, which is feasible with memoization and pruning.
Interview Verdict: Accepted / Efficient for interview constraints
This approach is optimal for the problem size and shows mastery of bitmasking and memoization.