1. 问题描述:
还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。
示例 1:
输入: [1,1,2,2,2]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。
示例 2:
输入: [3,3,3,3,4]
输出: false
解释: 不能用所有火柴拼成一个正方形。
注意:
给定的火柴长度和在 0 到 10 ^ 9之间。
火柴数组的长度不超过15。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/matchsticks-to-square
2. 思路分析:
这道题目属于dfs剪枝的经典问题,涉及到比较多剪枝的细节,其实可以记熟里面的技巧与方法这样在遇到类似的题目可以使用上相同的技巧。对于这道题目我们可以分为以下几种情况进行剪枝:
- 先对火柴的长度由大到小进行排序,在搜索的时候可以由长度较长的火柴开始搜索,长度较长的火柴对应的分支数目肯定是比较少的,这样在一开始的时候避免很多分支的搜索
- 我们可以每条边长每条边长拼凑,这样当我们拼成了三条边长的时候剩下那一条边就不用拼了,因为总和是固定的,前三条边已经拼好了那么最后一条边也一定可以拼出来。并且每一条边长我们可以按照火柴编号递增搜索,这样可以避免因为火柴顺序不一样但是总和一样的重复搜索,例如1,3,2与1,2,3其实是一样的,只是顺序不一样而已
- 若某一根火柴拼凑失败了,那么又可以分为以下三种情况:
- 跳过长度相邻位置相同长度的火柴
- 当前是第一根火柴
- 当前是最后一根火柴
3. 代码如下:
from typing import List
class Solution:
# start表示第几根火柴, cur表示当前拼成的火柴长度, l表示每根火柴需要拼成的长度, count表示拼成了多少条火柴, sta为标记列表标记哪些火柴是已经使用过了, nums是火柴长度
def dfs(self, start: int, cur: int, l: int, count: int, sta: List[int], nums: List[int]):
# 当前已经拼好了三根火柴
if count == 3: return True
# 拼好了当前的这条边长, 接下来拼下一个边长
if cur == l: return self.dfs(0, 0, l, count + 1, sta, nums)
i = start
while i < len(sta):
if sta[i] == 1:
i += 1
continue
if cur + nums[i] <= l:
sta[i] = 1
if self.dfs(i + 1, cur + nums[i], l, count, sta, nums):return True
# 回溯
sta[i] = 0
# 当前第一根或者最后一根不满足条件的时候直接减掉
if cur == 0 or cur + nums[i] == l: return False
i += 1
# 跳过当前的相邻的相同长度的火柴
while i < len(sta) and nums[i] == nums[i - 1]: i += 1
return False
def makesquare(self, nums: List[int]) -> bool:
if not nums: return False
# 从大到小排序
nums.sort(reverse=True)
sta = [0] * len(nums)
s = 0
for x in nums:
s += x
if s % 4: return False
return self.dfs(0, 0, s // 4, 0, sta, nums)