Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

473. Matchsticks to Square - Leetcode Solution

Problem Description

You are given an array of integers called matchsticks, where each element represents the length of a matchstick. Your task is to determine if you can use all of the matchsticks to form a square. Each matchstick must be used exactly once, and you cannot break any matchstick.

The goal is to partition the matchsticks into four groups, where the sum of the lengths in each group is equal. No matchstick can be reused, and all must be used. If it is possible to form such a square, return true; otherwise, return false.

  • Each matchstick must be used exactly once.
  • No matchstick can be broken or reused.
  • Return true if you can form a square, false otherwise.

Thought Process

At first glance, the problem seems similar to partitioning or subset sum problems. The brute-force approach would be to try every possible way to group the matchsticks into four equal-length sides. However, this approach quickly becomes infeasible as the number of matchsticks increases, because the number of possible groupings grows exponentially.

To optimize, we can leverage the fact that the sides must all be equal. If the sum of all matchsticks is not divisible by 4, it's immediately impossible. We also realize that placing the largest matchsticks first can help prune unworkable paths early. Using backtracking, we can try to assign each matchstick to one of four sides, ensuring no side exceeds the target length. If we successfully assign all matchsticks, we've found a solution.

The challenge is to efficiently explore the possible assignments without redundant work, using strategies like sorting and early pruning to avoid unnecessary computation.

Solution Approach

We'll use a backtracking algorithm to assign each matchstick to one of the four sides, ensuring that no side exceeds the required length. Here is a step-by-step breakdown:

  1. Check total length: Calculate the sum of all matchsticks. If the sum is not divisible by 4, it's impossible to form a square, so return false.
  2. Determine side length: The required length for each side is total_sum / 4.
  3. Sort the matchsticks: Sort the array in descending order. This helps by placing larger matchsticks first, which can quickly reveal impossible assignments.
  4. Backtracking function: Recursively try to assign each matchstick to a side. For each matchstick, attempt to add it to each side if it doesn't exceed the side length.
    • If a matchstick fits, assign it and recurse for the next matchstick.
    • If all matchsticks are assigned and all sides are equal to the target, return true.
    • If a matchstick doesn't fit on any side, backtrack.
  5. Prune search space: If adding a matchstick to a side doesn't work, and that side is currently zero, don't try other sides (to avoid redundant permutations).

This approach ensures that we check all valid configurations efficiently, skipping over impossible or repeated states.

Example Walkthrough

Let's consider the input matchsticks = [1,1,2,2,2].

  • Total sum = 8. Each side must be 2.
  • Sorted: [2,2,2,1,1]
  • Start assigning:
    • First 2 goes to side 1 (side 1: 2)
    • Second 2 goes to side 2 (side 2: 2)
    • Third 2 goes to side 3 (side 3: 2)
    • First 1 can go to side 4 (side 4: 1)
    • Second 1 can go to side 4 (side 4: 2)
  • All sides: [2,2,2,2]. Success! Return true.

The backtracking explores possibilities, but sorting and pruning quickly lead to a valid solution.

Time and Space Complexity

Brute-force Approach: The number of ways to assign n matchsticks to 4 sides is 4^n, which is exponential and infeasible for large n.

Optimized Backtracking Approach:

  • Time Complexity: Still potentially O(4^n) in the worst case, because each matchstick can go to any of the 4 sides. However, sorting and pruning (skipping impossible paths early) make it much faster in practice.
  • Space Complexity: O(n) for the recursion stack and the array tracking side lengths.

The optimizations ensure that many redundant or impossible branches are not explored, making it efficient for typical input sizes.

Summary

The "Matchsticks to Square" problem is a classic example of partitioning with constraints. By leveraging backtracking with pruning and sorting, we efficiently explore only the viable ways to assign matchsticks to sides. The key insights are early elimination of impossible cases, working with the largest matchsticks first, and avoiding redundant states. This results in a solution that is both elegant and practical for real-world input sizes.

Code Implementation

class Solution:
    def makesquare(self, matchsticks):
        if not matchsticks or len(matchsticks) < 4:
            return False
        total = sum(matchsticks)
        if total % 4 != 0:
            return False
        side = total // 4
        matchsticks.sort(reverse=True)
        sides = [0] * 4

        def backtrack(index):
            if index == len(matchsticks):
                return all(s == side for s in sides)
            for i in range(4):
                if sides[i] + matchsticks[index] <= side:
                    sides[i] += matchsticks[index]
                    if backtrack(index + 1):
                        return True
                    sides[i] -= matchsticks[index]
                if sides[i] == 0:
                    break
            return False

        return backtrack(0)
      
class Solution {
public:
    bool makesquare(vector<int>& matchsticks) {
        if (matchsticks.size() < 4) return false;
        int total = accumulate(matchsticks.begin(), matchsticks.end(), 0);
        if (total % 4 != 0) return false;
        int side = total / 4;
        sort(matchsticks.rbegin(), matchsticks.rend());
        vector<int> sides(4, 0);
        return backtrack(matchsticks, sides, 0, side);
    }
    bool backtrack(vector<int>& matchsticks, vector<int>& sides, int index, int target) {
        if (index == matchsticks.size())
            return sides[0]==target && sides[1]==target && sides[2]==target && sides[3]==target;
        for (int i = 0; i < 4; ++i) {
            if (sides[i] + matchsticks[index] <= target) {
                sides[i] += matchsticks[index];
                if (backtrack(matchsticks, sides, index+1, target))
                    return true;
                sides[i] -= matchsticks[index];
            }
            if (sides[i] == 0) break;
        }
        return false;
    }
};
      
class Solution {
    public boolean makesquare(int[] matchsticks) {
        if (matchsticks == null || matchsticks.length < 4) return false;
        int total = 0;
        for (int m : matchsticks) total += m;
        if (total % 4 != 0) return false;
        int side = total / 4;
        Arrays.sort(matchsticks);
        int n = matchsticks.length;
        // reverse sort
        for (int i = 0; i < n / 2; i++) {
            int tmp = matchsticks[i];
            matchsticks[i] = matchsticks[n - 1 - i];
            matchsticks[n - 1 - i] = tmp;
        }
        int[] sides = new int[4];
        return backtrack(matchsticks, sides, 0, side);
    }
    private boolean backtrack(int[] matchsticks, int[] sides, int index, int target) {
        if (index == matchsticks.length)
            return sides[0]==target && sides[1]==target && sides[2]==target && sides[3]==target;
        for (int i = 0; i < 4; i++) {
            if (sides[i] + matchsticks[index] <= target) {
                sides[i] += matchsticks[index];
                if (backtrack(matchsticks, sides, index + 1, target))
                    return true;
                sides[i] -= matchsticks[index];
            }
            if (sides[i] == 0) break;
        }
        return false;
    }
}
      
var makesquare = function(matchsticks) {
    if (!matchsticks || matchsticks.length < 4) return false;
    let total = matchsticks.reduce((a, b) => a + b, 0);
    if (total % 4 !== 0) return false;
    let side = total / 4;
    matchsticks.sort((a, b) => b - a);
    let sides = [0, 0, 0, 0];

    function backtrack(index) {
        if (index === matchsticks.length)
            return sides[0] === side && sides[1] === side && sides[2] === side && sides[3] === side;
        for (let i = 0; i < 4; i++) {
            if (sides[i] + matchsticks[index] <= side) {
                sides[i] += matchsticks[index];
                if (backtrack(index + 1)) return true;
                sides[i] -= matchsticks[index];
            }
            if (sides[i] === 0) break;
        }
        return false;
    }

    return backtrack(0);
};