Adding up the matchsticks gives the square’s and the square’s expected side length is
. Now, using all the matchsticks exactly once, we need to build the four sides–each side must have the expected length.
Backtracking
From a side’s point of view
We try making one side at a time using all combinations of unused sticks that has a total side length == expected_side_len. If longest stick is longer than the expected_side_len, we cannot make a square. We start with matchsticks sorted by length so as soon as a combination-sum is too big, latter sticks would not help that combination, so we can backtrack and try other combinations.
Say shortest stick’s length is and expected side length is
, then the max recursion depth is
. If there are
matchsticks, the cost of levels are like:
. In other words, time complexity:
, space complexity:
.
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
def try_make_square(nsides: int, side_len: int, curr_len: int, used: Set[int]) -> bool:
if nsides == 4:
return True
for i, stick_len in enumerate(matchsticks):
if i in used:
continue
new_len = curr_len + stick_len
if new_len > side_len:
continue
used.add(i)
if new_len == side_len:
if try_make_square(nsides+1, side_len, 0, used):
return True
else:
if try_make_square(nsides, side_len, new_len, used):
return True
used.remove(i)
return False
perimeter = sum(matchsticks)
side_len, r = divmod(perimeter, 4)
if r != 0:
return False
if max(matchsticks) > side_len:
return False
return try_make_square(0, side_len, 0, set())
From a matchstick’s point of view
From a stick’s point of view, it must be assigned to one of the four sides. So, we can scan matchsticks left to right and try assigning current stick to each of the four sides. When all sticks have been assigned and each side has a total length == expected_side_len, we have been able to make the square.
Given there are matchsticks, the recursion depth is now
and the width of a level is at most
. Time complexity:
, space complexity:
.
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
def try_assign_to_side(
stick_index: int, sides: List[List[int]], expected_side_len: int
) -> bool:
if stick_index >= len(matchsticks):
return all(sum(sticks) == expected_side_len for sticks in sides)
current_stick_len = matchsticks[stick_index]
for j in range(4):
new_side_len = sum(sides[j]) + current_stick_len
if new_side_len > expected_side_len:
continue
sides[j].append(current_stick_len)
if try_assign_to_side(stick_index + 1, sides, expected_side_len):
return True
sides[j].pop()
return False
perimeter = sum(matchsticks)
expected_side_len, r = divmod(perimeter, 4)
if r != 0:
return False
if max(matchsticks) > expected_side_len:
return False
sides = [[] for _ in range(4)]
return try_assign_to_side(0, sides, expected_side_len)
Like packing a bag, once big items have been placed, tiny items can be inserted in leftover nooks and crannies. So, assigning the matchsticks in a certain order—starting with the longest going towards shortest—may help.
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
peri = sum(matchsticks)
expected_side, extra = divmod(peri, 4)
if extra:
return False
if max(matchsticks) > expected_side:
return False
sides = [0] * 4
def can_make(i):
nonlocal sides, expected_side
if i == len(matchsticks):
return sides[0] == sides[1] == sides[2] == sides[3]
for j in range(4):
if sides[j] + matchsticks[i] > expected_side:
continue
sides[j] += matchsticks[i]
if can_make(i + 1):
return True
sides[j] -= matchsticks[i]
return False
matchsticks.sort(reverse=True)
return can_make(0)
Leave a comment