A clear explanation of deciding whether matchsticks can form a square using backtracking, sorting, and pruning.
Problem Restatement
We are given an integer array matchsticks.
Each value represents the length of one matchstick.
We must use every matchstick exactly once.
We need to determine whether all matchsticks can form a square.
A square has four sides of equal length, so the task is to divide the matchsticks into four groups with the same sum. The official problem asks whether the given matchsticks can make one square, using every matchstick exactly once.
Input and Output
| Item | Meaning |
|---|---|
| Input | matchsticks, a list of positive integers |
| Output | True if they can form a square, otherwise False |
| Rule | Every matchstick must be used exactly once |
| Square condition | Four sides must have equal total length |
Function shape:
def makesquare(matchsticks: list[int]) -> bool:
...Examples
Example 1:
matchsticks = [1, 1, 2, 2, 2]The total length is:
1 + 1 + 2 + 2 + 2 = 8Each side must have length:
8 // 4 = 2We can form four sides:
[2]
[2]
[2]
[1, 1]Answer:
TrueExample 2:
matchsticks = [3, 3, 3, 3, 4]The total length is:
16Each side would need length:
4But the matchstick of length 4 forms one side, and each 3 would need another 1, which does not exist.
Answer:
FalseFirst Thought: Try Every Assignment
Each matchstick must go to one of four sides.
A direct recursive idea is:
- Pick a matchstick.
- Try putting it on side
0. - Try putting it on side
1. - Try putting it on side
2. - Try putting it on side
3.
If all matchsticks are placed and all four side sums are equal, return True.
This is the right search structure.
The main issue is pruning the search enough.
Problem With Plain Brute Force
If there are n matchsticks, and each one can go to four sides, the naive search has up to:
4^nstates.
That is too large.
But we can prune heavily:
- If the total length is not divisible by
4, returnFalse. - If any matchstick is longer than the target side length, return
False. - Sort matchsticks in descending order so large sticks are placed first.
- Avoid trying equivalent empty sides repeatedly.
- Never let a side exceed the target length.
Key Insight
The problem is equivalent to partitioning the array into four equal-sum groups.
Let:
target = sum(matchsticks) // 4We keep four running side sums:
sides = [0, 0, 0, 0]Then we place matchsticks one by one.
A placement is valid only if:
sides[i] + stick <= targetIf we can place every stick, then all four sides must equal target, because the total sum is exactly 4 * target.
Algorithm
First compute the total sum.
If it is not divisible by 4, return False.
Compute:
target = total // 4If the largest matchstick is greater than target, return False.
Sort matchsticks in descending order.
Run DFS:
dfs(index)where index is the next matchstick to place.
For the current stick, try each of the four sides.
If placing it does not exceed target, place it and recurse.
If recursion succeeds, return True.
Undo the placement and try another side.
If no side works, return False.
Walkthrough
Use:
matchsticks = [1, 1, 2, 2, 2]Total:
8Target side length:
2Sort descending:
[2, 2, 2, 1, 1]Start with:
sides = [0, 0, 0, 0]Place first 2:
[2, 0, 0, 0]Place second 2:
[2, 2, 0, 0]Place third 2:
[2, 2, 2, 0]Place first 1:
[2, 2, 2, 1]Place second 1:
[2, 2, 2, 2]All sticks are used.
Answer:
TrueCorrectness
The algorithm only places a matchstick onto a side when the side sum does not exceed the target side length. Therefore, it never creates an invalid side.
The DFS tries every possible assignment of matchsticks to sides that respects the target limit. If a valid square exists, then there is some sequence of side choices that corresponds to that square. The DFS will eventually try that sequence and return True.
If the DFS returns True, every matchstick has been assigned. Since the total sum is 4 * target and no side exceeds target, all four sides must equal target. Therefore, the matchsticks form a valid square.
If the DFS exhausts all valid assignments and returns False, then no partition into four equal target sums exists.
Thus, the algorithm returns the correct answer.
Complexity
Let n = len(matchsticks).
| Metric | Value | Why |
|---|---|---|
| Time | O(4^n) worst case | Each stick may try four sides |
| Space | O(n) | Recursion depth is at most n |
Sorting and pruning make the practical runtime much smaller.
Implementation
class Solution:
def makesquare(self, matchsticks: list[int]) -> bool:
total = sum(matchsticks)
if total % 4 != 0:
return False
target = total // 4
matchsticks.sort(reverse=True)
if matchsticks[0] > target:
return False
sides = [0, 0, 0, 0]
def dfs(index: int) -> bool:
if index == len(matchsticks):
return True
stick = matchsticks[index]
for side in range(4):
if sides[side] + stick > target:
continue
sides[side] += stick
if dfs(index + 1):
return True
sides[side] -= stick
if sides[side] == 0:
break
return False
return dfs(0)Code Explanation
We first compute the total length:
total = sum(matchsticks)If the total cannot be split into four equal sides, no square is possible:
if total % 4 != 0:
return FalseThe target side length is:
target = total // 4Then we sort in descending order:
matchsticks.sort(reverse=True)This places large matchsticks first, which usually detects impossible branches earlier.
If the largest stick is already too long, no side can contain it:
if matchsticks[0] > target:
return FalseThe array:
sides = [0, 0, 0, 0]stores the current length of each side.
The DFS places one stick at a time:
stick = matchsticks[index]For each side, we check whether the stick fits:
if sides[side] + stick > target:
continueThen we place it:
sides[side] += stickIf the recursive call succeeds, we return immediately:
if dfs(index + 1):
return TrueOtherwise, we undo the placement:
sides[side] -= stickThe pruning rule:
if sides[side] == 0:
breakmeans that if placing the stick on an empty side did not work, trying another empty side is equivalent. All empty sides are symmetric.
Testing
def run_tests():
s = Solution()
assert s.makesquare([1, 1, 2, 2, 2]) is True
assert s.makesquare([3, 3, 3, 3, 4]) is False
assert s.makesquare([1, 1, 1, 1]) is True
assert s.makesquare([5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3]) is True
assert s.makesquare([1, 1, 1, 2, 2, 2, 3, 3]) is False
assert s.makesquare([10, 6, 5, 5, 5, 3, 3, 3, 2, 2, 2, 2]) is True
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1,1,2,2,2] | Standard positive case |
[3,3,3,3,4] | Total divisible, but impossible partition |
[1,1,1,1] | Smallest square |
| Mixed large groups | Checks non-trivial valid partition |
[1,1,1,2,2,2,3,3] | Divisibility alone is not enough |
| Larger valid case | Checks deeper backtracking |