题目描述:
你将得到一个整数数组 matchsticks ,其中 matchsticks[i] 是第 i 个火柴棒的长度。你要用 所有的火柴棍 拼成一个正方形。你 不能折断 任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须 使用一次 。
如果你能使这个正方形,则返回 true ,否则返回 false 。
示例1:
输入: matchsticks = [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: matchsticks = [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
思路1: 回溯的方式 类似于八皇后问题
因为要构成一个正方形且每根火柴都要使用,所以首先可以通过几种特殊情况,确定为false:
1)数组为空;数组长度小于4
2)数组求和之后除以4的值不是整数
排除以上情况之后,我们知道正方形有4条边,我们对每一根火柴在四条边中找位置,进行深搜,看是否所以火柴都能找到一个位置,由于数组可能很大,所以我们在深搜的时候,要注意时间不能超时。
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
if not matchsticks or len(matchsticks) < 4:
return False
s = sum(matchsticks)
if s % 4 != 0:
return False
l = len(matchsticks)
self.bian = s / 4
self.edge = [0] * 4 # 一共四条边,值为当前边的长度
matchsticks.sort(reverse=True) # 不排序将超时,排序的话可以减少一些递归的次数, 还要注意一定要是降序排列,将大的放到前面,可以一定程度减少递归的次数
def dfs(number) -> bool: # 这个函数可以放在所求函数里,也可以单独放一个函数
if number == l: # 所有的火柴都找到了自己的位置
return True
else:
for i in range(4): # 对四条边都尝试一下
self.edge[i] += matchsticks[number]
if self.edge[i] <= self.bian and dfs(number+1):
return True
else:
self.edge[i] -= matchsticks[number]
return False
return dfs(0) # 从第一个元素开始放
可见这个思路是用空间换时间。
时间复杂度:O(4^ n) ,其中 n 是火柴的数目。每根火柴都可以选择放在 4 条边上。
空间复杂度:O(n)。递归栈需要占用 O(n) 的空间。
思路2:
官方题解中给出了一个状态压缩+动态规划的方式,可以减少时间复杂度,但是我看不懂。
class Solution:
def makesquare(self, matchsticks: List[int]) -> bool:
totalLen = sum(matchsticks)
if totalLen % 4:
return False
tLen = totalLen // 4
dp = [-1] * (1 << len(matchsticks))
dp[0] = 0
for s in range(1, len(dp)):
for k, v in enumerate(matchsticks):
if s & (1 << k) == 0:
continue
s1 = s & ~(1 << k)
if dp[s1] >= 0 and dp[s1] + v <= tLen:
dp[s] = (dp[s1] + v) % tLen
break
return dp[-1] == 0
时间复杂度:O(n * 2 ^ n ) , 其中 nn 是火柴的数目。总共有 2^n个状态,计算每个状态都需要 O(n)。
空间复杂度:O(2^ n)。保存数组dp 需要 O(2^n) 的空间。