473. Matchsticks to Square
Problem Description
You have an array of integers called matchsticks
where each element matchsticks[i]
represents the length of the i-th matchstick. Your goal is to use all the matchsticks to form a square.
The rules are:
- You must use every single matchstick exactly once
- You cannot break any matchstick into smaller pieces
- You can connect matchsticks end-to-end to form the sides of the square
- All four sides of the square must have equal length
Return true
if it's possible to arrange all the matchsticks to form a square, otherwise return false
.
For example, if you have matchsticks with lengths [1,1,2,2,2]
, you can form a square with side length 2 by arranging them as four sides each of length 2. However, if you have matchsticks [3,3,3,3,4]
, you cannot form a square because the total length is 16, which would require sides of length 4, but you cannot create four sides of equal length 4 with the given matchsticks.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem involves an array of matchstick lengths, not nodes and edges in a graph structure.
Need to solve for kth smallest/largest?
- No: We're not looking for the kth smallest or largest element. We need to determine if we can partition the matchsticks into 4 equal groups.
Involves Linked Lists?
- No: The problem uses an array of integers, not linked lists.
Does the problem have small constraints?
- Yes: The problem has relatively small constraints (typically matchsticks array length ≤ 15). We need to try different combinations of matchsticks to form 4 equal-length sides, which involves exploring many possible arrangements.
Brute force / Backtracking?
- Yes: Since we need to try placing each matchstick into one of 4 groups (sides of the square) and backtrack when a placement doesn't work, backtracking is the appropriate approach.
Conclusion: The flowchart correctly leads us to use a Backtracking approach. This makes sense because:
- We need to explore all possible ways to distribute matchsticks among 4 sides
- We can prune invalid paths early (when a side exceeds the target length)
- We need to backtrack when a particular assignment doesn't lead to a solution
- The small constraints make the exponential time complexity of backtracking feasible
Intuition
To form a square using all matchsticks, we first need to determine what the side length should be. Since a square has 4 equal sides and we must use all matchsticks, the total length of all matchsticks must be divisible by 4. If sum(matchsticks) % 4 != 0
, it's impossible to form a square.
The target side length would be sum(matchsticks) / 4
. Additionally, no single matchstick can be longer than this target side length, otherwise it couldn't fit into any side.
Now comes the key insight: we need to partition all matchsticks into exactly 4 groups where each group's sum equals the target side length. This is essentially a partition problem with backtracking.
Think of it like having 4 buckets (representing the 4 sides of the square). We try to place each matchstick into one of these buckets. For each matchstick, we have 4 choices - which bucket to place it in. After placing a matchstick, we recursively try to place the next one. If at any point a bucket's total exceeds the target side length, we know this path won't work and we backtrack.
To optimize the search, we can sort the matchsticks in descending order. This way, we try to place longer matchsticks first, which helps us fail faster if a solution doesn't exist. Longer matchsticks have fewer valid placement options, so deciding their placement early reduces the search space.
Another optimization is to skip duplicate states. If two buckets currently have the same total length, trying to place a matchstick in either one would lead to equivalent states, so we can skip one of them.
The recursive process continues until either all matchsticks are successfully placed (return true
) or we've exhausted all possibilities (return false
).
Solution Approach
The implementation uses a depth-first search (DFS) with backtracking to solve this partition problem.
Initial Validation: First, we calculate the target side length and check if a solution is possible:
x, mod = divmod(sum(matchsticks), 4)
if mod or x < max(matchsticks):
return False
x
is the target side length (total sum divided by 4)mod
is the remainder - if it's not 0, we can't form equal sides- If any matchstick is longer than
x
, it's impossible to form a square
Data Structures:
edges = [0] * 4
: An array tracking the current length of each of the 4 sidesmatchsticks.sort(reverse=True)
: Sort matchsticks in descending order for optimization
DFS Backtracking Function:
The core logic is in the dfs(u)
function where u
is the index of the current matchstick to place:
def dfs(u):
if u == len(matchsticks):
return True # All matchsticks placed successfully
For each matchstick at index u
, we try placing it on each of the 4 sides:
for i in range(4):
if i > 0 and edges[i - 1] == edges[i]:
continue # Skip duplicate states
edges[i] += matchsticks[u] # Place matchstick on side i
if edges[i] <= x and dfs(u + 1): # Check validity and recurse
return True
edges[i] -= matchsticks[u] # Backtrack
Key Optimizations:
-
Sorting in descending order: By placing larger matchsticks first, we can prune invalid branches earlier in the search tree.
-
Duplicate state pruning: If two sides currently have the same length, placing the next matchstick on either side leads to equivalent states. The condition
if i > 0 and edges[i - 1] == edges[i]: continue
skips these redundant explorations. -
Early termination: We only continue recursion if
edges[i] <= x
, immediately pruning branches where a side exceeds the target length.
The algorithm explores all valid ways to distribute matchsticks among the 4 sides, backtracking whenever it reaches an invalid state, until it either finds a valid configuration or exhausts all possibilities.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with matchsticks = [1, 1, 2, 2, 2]
.
Step 1: Initial Validation
- Total sum = 1 + 1 + 2 + 2 + 2 = 8
- Target side length = 8 ÷ 4 = 2
- Remainder = 0 ✓ (divisible by 4)
- Max matchstick = 2, which equals target ✓ (no matchstick too long)
Step 2: Sort and Initialize
- After sorting in descending order:
[2, 2, 2, 1, 1]
- Initialize edges =
[0, 0, 0, 0]
(representing 4 sides)
Step 3: DFS Backtracking Process
Placing matchstick[0] = 2:
- Try side 0: edges =
[2, 0, 0, 0]
✓ (2 ≤ target) - Continue with next matchstick
Placing matchstick[1] = 2:
- Skip side 0: would make it 4 > target ✗
- Try side 1: edges =
[2, 2, 0, 0]
✓ - Continue with next matchstick
Placing matchstick[2] = 2:
- Skip sides 0 and 1: would exceed target
- Try side 2: edges =
[2, 2, 2, 0]
✓ - Continue with next matchstick
Placing matchstick[3] = 1:
- Skip sides 0, 1, 2: would exceed target
- Try side 3: edges =
[2, 2, 2, 1]
✓ - Continue with next matchstick
Placing matchstick[4] = 1:
- Skip sides 0, 1, 2: would exceed target
- Try side 3: edges =
[2, 2, 2, 2]
✓ - All matchsticks placed!
Step 4: Success
All sides equal 2, return true
.
Key Observations:
- Sorting helped us place the larger matchsticks (2's) first, establishing the structure early
- The duplicate state pruning wasn't triggered in this example since sides had different values when we were placing matchsticks
- If we had started with smaller matchsticks, we'd have many more combinations to explore (e.g., two 1's could go on the same side or different sides), making the search less efficient
Solution Implementation
1class Solution:
2 def makesquare(self, matchsticks: List[int]) -> bool:
3 """
4 Determine if all matchsticks can form a square.
5
6 Args:
7 matchsticks: List of integers representing matchstick lengths
8
9 Returns:
10 True if matchsticks can form a square, False otherwise
11 """
12
13 def backtrack(stick_index):
14 """
15 Try to place each matchstick into one of the four sides using backtracking.
16
17 Args:
18 stick_index: Current index of matchstick being placed
19
20 Returns:
21 True if all matchsticks can be placed to form a square
22 """
23 # Base case: all matchsticks have been successfully placed
24 if stick_index == len(matchsticks):
25 return True
26
27 # Try placing current matchstick in each of the 4 sides
28 for side_index in range(4):
29 # Optimization: skip if current side has same length as previous side
30 # (avoids redundant computation for identical states)
31 if side_index > 0 and side_lengths[side_index - 1] == side_lengths[side_index]:
32 continue
33
34 # Place current matchstick on this side
35 side_lengths[side_index] += matchsticks[stick_index]
36
37 # If side length doesn't exceed target and we can place remaining sticks
38 if side_lengths[side_index] <= target_side_length and backtrack(stick_index + 1):
39 return True
40
41 # Backtrack: remove current matchstick from this side
42 side_lengths[side_index] -= matchsticks[stick_index]
43
44 return False
45
46 # Calculate target side length for the square
47 total_length = sum(matchsticks)
48 target_side_length, remainder = divmod(total_length, 4)
49
50 # Early termination: impossible if total isn't divisible by 4
51 # or if any single matchstick is longer than target side length
52 if remainder != 0 or target_side_length < max(matchsticks):
53 return False
54
55 # Initialize four sides with length 0
56 side_lengths = [0] * 4
57
58 # Sort matchsticks in descending order for optimization
59 # (placing longer sticks first reduces search space)
60 matchsticks.sort(reverse=True)
61
62 # Start backtracking from first matchstick
63 return backtrack(0)
64
1class Solution {
2 public boolean makesquare(int[] matchsticks) {
3 // Calculate total sum and find maximum matchstick length
4 int totalSum = 0;
5 int maxLength = 0;
6 for (int matchstick : matchsticks) {
7 totalSum += matchstick;
8 maxLength = Math.max(maxLength, matchstick);
9 }
10
11 // Calculate target side length for the square
12 int targetSideLength = totalSum / 4;
13 int remainder = totalSum % 4;
14
15 // Early termination: impossible to form a square if:
16 // 1. Total sum is not divisible by 4
17 // 2. Any matchstick is longer than the target side length
18 if (remainder != 0 || targetSideLength < maxLength) {
19 return false;
20 }
21
22 // Sort matchsticks to optimize backtracking (try larger matchsticks first)
23 Arrays.sort(matchsticks);
24
25 // Initialize array to track the current sum of each of the 4 sides
26 int[] sides = new int[4];
27
28 // Start DFS from the last index (largest matchstick after sorting)
29 return dfs(matchsticks.length - 1, targetSideLength, matchsticks, sides);
30 }
31
32 private boolean dfs(int currentIndex, int targetSideLength, int[] matchsticks, int[] sides) {
33 // Base case: all matchsticks have been successfully placed
34 if (currentIndex < 0) {
35 return true;
36 }
37
38 // Try placing current matchstick on each of the 4 sides
39 for (int sideIndex = 0; sideIndex < 4; sideIndex++) {
40 // Optimization: skip if previous side has same length (avoid duplicate states)
41 if (sideIndex > 0 && sides[sideIndex - 1] == sides[sideIndex]) {
42 continue;
43 }
44
45 // Try placing current matchstick on this side
46 sides[sideIndex] += matchsticks[currentIndex];
47
48 // If this placement is valid and leads to a solution, return true
49 if (sides[sideIndex] <= targetSideLength &&
50 dfs(currentIndex - 1, targetSideLength, matchsticks, sides)) {
51 return true;
52 }
53
54 // Backtrack: remove the matchstick from this side
55 sides[sideIndex] -= matchsticks[currentIndex];
56 }
57
58 // No valid placement found for current matchstick
59 return false;
60 }
61}
62
1class Solution {
2public:
3 bool makesquare(vector<int>& matchsticks) {
4 // Calculate total sum and find maximum matchstick length
5 int totalSum = 0;
6 int maxLength = 0;
7 for (int& length : matchsticks) {
8 totalSum += length;
9 maxLength = max(maxLength, length);
10 }
11
12 // Calculate target side length for the square
13 int targetSideLength = totalSum / 4;
14 int remainder = totalSum % 4;
15
16 // Early termination: impossible to form a square if:
17 // 1. Total sum is not divisible by 4
18 // 2. Any matchstick is longer than the target side length
19 if (remainder != 0 || targetSideLength < maxLength) {
20 return false;
21 }
22
23 // Sort matchsticks in descending order for optimization
24 // (try placing longer matchsticks first to fail faster)
25 sort(matchsticks.begin(), matchsticks.end(), greater<int>());
26
27 // Initialize four sides of the square
28 vector<int> sides(4, 0);
29
30 // Start DFS from the first matchstick
31 return dfs(0, targetSideLength, matchsticks, sides);
32 }
33
34private:
35 bool dfs(int currentIndex, int targetSideLength,
36 vector<int>& matchsticks, vector<int>& sides) {
37 // Base case: all matchsticks have been placed successfully
38 if (currentIndex == matchsticks.size()) {
39 return true;
40 }
41
42 // Try placing current matchstick on each of the four sides
43 for (int sideIndex = 0; sideIndex < 4; ++sideIndex) {
44 // Optimization: skip if current side has same length as previous side
45 // (avoids redundant permutations)
46 if (sideIndex > 0 && sides[sideIndex - 1] == sides[sideIndex]) {
47 continue;
48 }
49
50 // Try placing current matchstick on this side
51 sides[sideIndex] += matchsticks[currentIndex];
52
53 // If placement is valid, continue with next matchstick
54 if (sides[sideIndex] <= targetSideLength &&
55 dfs(currentIndex + 1, targetSideLength, matchsticks, sides)) {
56 return true;
57 }
58
59 // Backtrack: remove current matchstick from this side
60 sides[sideIndex] -= matchsticks[currentIndex];
61 }
62
63 // No valid placement found for current matchstick
64 return false;
65 }
66};
67
1function makesquare(matchsticks: number[]): boolean {
2 // Calculate total sum and find maximum matchstick length
3 let totalSum = 0;
4 let maxLength = 0;
5 for (const length of matchsticks) {
6 totalSum += length;
7 maxLength = Math.max(maxLength, length);
8 }
9
10 // Calculate target side length for the square
11 const targetSideLength = Math.floor(totalSum / 4);
12 const remainder = totalSum % 4;
13
14 // Early termination: impossible to form a square if:
15 // 1. Total sum is not divisible by 4
16 // 2. Any matchstick is longer than the target side length
17 if (remainder !== 0 || targetSideLength < maxLength) {
18 return false;
19 }
20
21 // Sort matchsticks in descending order for optimization
22 // (try placing longer matchsticks first to fail faster)
23 matchsticks.sort((a, b) => b - a);
24
25 // Initialize four sides of the square
26 const sides = new Array(4).fill(0);
27
28 // Start DFS from the first matchstick
29 return dfs(0, targetSideLength, matchsticks, sides);
30}
31
32function dfs(
33 currentIndex: number,
34 targetSideLength: number,
35 matchsticks: number[],
36 sides: number[]
37): boolean {
38 // Base case: all matchsticks have been placed successfully
39 if (currentIndex === matchsticks.length) {
40 return true;
41 }
42
43 // Try placing current matchstick on each of the four sides
44 for (let sideIndex = 0; sideIndex < 4; sideIndex++) {
45 // Optimization: skip if current side has same length as previous side
46 // (avoids redundant permutations)
47 if (sideIndex > 0 && sides[sideIndex - 1] === sides[sideIndex]) {
48 continue;
49 }
50
51 // Try placing current matchstick on this side
52 sides[sideIndex] += matchsticks[currentIndex];
53
54 // If placement is valid, continue with next matchstick
55 if (sides[sideIndex] <= targetSideLength &&
56 dfs(currentIndex + 1, targetSideLength, matchsticks, sides)) {
57 return true;
58 }
59
60 // Backtrack: remove current matchstick from this side
61 sides[sideIndex] -= matchsticks[currentIndex];
62 }
63
64 // No valid placement found for current matchstick
65 return false;
66}
67
Time and Space Complexity
Time Complexity: O(4^n)
where n
is the number of matchsticks.
The algorithm uses backtracking with DFS to try placing each matchstick into one of 4 possible sides. For each matchstick, we have at most 4 choices (which side to place it on). In the worst case, we explore all possible combinations before finding a solution or determining it's impossible. Although there are pruning optimizations like:
- Sorting matchsticks in descending order to fail faster
- Skipping duplicate edge values (
if i > 0 and edges[i - 1] == edges[i]: continue
) - Early termination when edge length exceeds target (
if edges[i] <= x
)
The worst-case time complexity remains O(4^n)
as we potentially need to explore the entire search tree.
Space Complexity: O(n)
The space complexity consists of:
- Recursion call stack depth:
O(n)
- the maximum depth isn
when we process all matchsticks - The
edges
array:O(4)
=O(1)
- constant space for storing 4 edge lengths - Input array sorting is done in-place:
O(1)
additional space
Therefore, the overall space complexity is O(n)
dominated by the recursion stack.
Common Pitfalls
1. Forgetting to Check Early Termination Conditions
A common mistake is diving straight into the backtracking algorithm without validating whether a solution is even possible. This leads to unnecessary computation and potential incorrect results.
Pitfall Example:
def makesquare(self, matchsticks: List[int]) -> bool:
target_side_length = sum(matchsticks) // 4 # Wrong: No validation!
side_lengths = [0] * 4
def backtrack(stick_index):
# ... backtracking logic
return backtrack(0)
Why it's problematic:
- If the total sum isn't divisible by 4, integer division gives an incorrect target
- If a single matchstick exceeds the target side length, the algorithm wastes time on an impossible problem
Solution: Always validate preconditions before starting the expensive backtracking:
total_length = sum(matchsticks)
target_side_length, remainder = divmod(total_length, 4)
# Must check both conditions
if remainder != 0 or target_side_length < max(matchsticks):
return False
2. Not Optimizing for Duplicate States
Another pitfall is exploring redundant states when multiple sides have the same current length, leading to exponential slowdown.
Pitfall Example:
for side_index in range(4):
# Missing optimization - explores duplicate states
side_lengths[side_index] += matchsticks[stick_index]
if side_lengths[side_index] <= target_side_length and backtrack(stick_index + 1):
return True
side_lengths[side_index] -= matchsticks[stick_index]
Why it's problematic: If sides 0 and 1 both have length 3, placing the next matchstick on either creates equivalent states. Without pruning, you explore both branches unnecessarily.
Solution: Skip sides with identical lengths to the previous side:
for side_index in range(4):
if side_index > 0 and side_lengths[side_index - 1] == side_lengths[side_index]:
continue # Skip duplicate state
3. Processing Matchsticks in Suboptimal Order
Processing matchsticks in their original order or ascending order can significantly increase the search space.
Pitfall Example:
# Using matchsticks in original order - inefficient! return backtrack(0)
Why it's problematic:
- Placing smaller matchsticks first creates more branching possibilities
- Invalid branches are discovered later in the search tree
- For example, with matchsticks [1,1,1,1,8], placing the small ones first explores many combinations before realizing 8 can't fit
Solution: Sort matchsticks in descending order before backtracking:
matchsticks.sort(reverse=True) # Place larger matchsticks first return backtrack(0)
This helps prune invalid branches early since larger matchsticks have fewer valid placement options.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Bitmask and Dynamic Programming Bit manipulation is a crucial aspect of computer programming and one of the most powerful tools for bit manipulation is bitmasks Let's first understand what a bit is A bit is a binary digit It's the smallest piece of data in a computer and can be
Want a Structured Path to Master System Design Too? Don’t Miss This!