Facebook Pixel

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:

  1. We need to explore all possible ways to distribute matchsticks among 4 sides
  2. We can prune invalid paths early (when a side exceeds the target length)
  3. We need to backtrack when a particular assignment doesn't lead to a solution
  4. The small constraints make the exponential time complexity of backtracking feasible
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 sides
  • matchsticks.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:

  1. Sorting in descending order: By placing larger matchsticks first, we can prune invalid branches earlier in the search tree.

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

  3. 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 Evaluator

Example 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 is n 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More