Leetcode 473:火柴拼正方形(超详细的解法!!!)

还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。

输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。

示例 1:

输入: [1,1,2,2,2]
输出: true

解释: 能拼成一个边长为2的正方形,每边两根火柴。

示例 2:

输入: [3,3,3,3,4]
输出: false

解释: 不能用所有火柴拼成一个正方形。

注意:

  • 给定的火柴长度和在010^9之间。
  • 火柴数组的长度不超过15

解题思路

我们可以采用dfs来处理这个问题,也就是暴力枚举每个边所需的火柴。

首先对数据进行一些预处理,判断len(nums)是不是没有4个?判断sum(nums)能否被4整除?

接着说一说dfs过程,这里的dfs主要有两种写法。第一种从前向后枚举所有的nums,然后考虑当前的nums[i]放到哪个边上去。最后当遍历到最后一个元素的时候,判断此时的所有边是不是满足条件即可。

class Solution:
    def makesquare(self, nums: List[int]) -> bool:
        s, n = sum(nums), len(nums)
        t = s//4
        if len(nums) < 4 or s % 4:
            return False
        
        data = [0] * 4

        def dfs(u):
            if u == n:
                if all(i == t for i in data):
                    return True
                return False
                
            for i in range(4):
                if data[i] + nums[u] > t: continue

                data[i] += nums[u]
                if dfs(u + 1): return True
                data[i] -= nums[u]
            return False
        
        return dfs(0)

第二种写法更为通用,依次枚举每条边并且判断每条边需要哪些火柴。这里的边界情况稍微复杂一些,当枚举完所有边的时候表示有解;如果当前边已经摆好了,那么我们需要摆下一个边。

class Solution:
    def makesquare(self, nums: List[int]) -> bool:
        s, n = sum(nums), len(nums)
        t = s//4
        if len(nums) < 4 or s % 4:
            return False

        vis = [0] * n
        
        def dfs(cur, u, k):
            """
            cur: 当前边(第u条边)的长度
            u: 第几条边
            k: nums中的第k个数
            """
            if u == 4: return True
            if cur == t: return dfs(0, u + 1, 0) # 摆下一个边
            
            i = k
            while i < n:
                if vis[i] or (cur + nums[i] > t): 
                    i += 1
                    continue
                    
                vis[i] = 1
                if dfs(cur + nums[i], u, i + 1): return True
                vis[i] = 0
                i += 1

            return False
        
        return dfs(0, 0, 0)

我们针对第二种写法做一些剪枝。

首先我们可以将nums按照从大到小的顺序排序,这样我们一开始的时候搜索的范围就会比较小。接着可以对相同大小的火柴进行处理,如果其中一个火柴无解,那么所有相同长度的火柴都无解。最后还有一个非常重要的剪枝,当每条边摆放第一个火柴的时候,如果此时无解,那么后面的所有边都无解(因为后面的边都没有摆放,所以和当前的边是一样的);当每条边摆放最后一个火柴的时候,如果此时无解,那么后面的所有边都无解(可以采用反证法证明:如果后面的某条边有解(将这个火柴放入后),那么我们可以将这个火柴放到那条边的最后,此时就和当前状态是一致的)。

class Solution:
    def makesquare(self, nums: List[int]) -> bool:
        s, n = sum(nums), len(nums)
        t = s//4
        if len(nums) < 4 or s % 4:
            return False
        
        nums.sort(reverse=True)
        vis = [0] * n
        
        def dfs(cur, u, k):
            if u == 4: return True
            if cur == t: return dfs(0, u + 1, 0)
            
            i = k
            while i < n:
                if vis[i] or (cur + nums[i] > t): 
                    i += 1
                    continue
                    
                vis[i] = 1
                if dfs(cur + nums[i], u, i + 1): return True
                vis[i] = 0
                
                # 针对每条边的第一个和最后一个火柴
                if not cur or cur + nums[i] == t: return False 
                
                j = i
                while j < n and nums[i] == nums[j]: j += 1 # 相同火柴
                i = j - 1
                i += 1
            return False
        
        return dfs(0, 0, 0)

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

你可能感兴趣的:(leetcode解题指南,Problems)