leetcode 473. 火柴拼正方形-思路整理与细节分析

题目:

你将得到一个整数数组matchsticks,其中 matchsticks[i]是第i个火柴棒的长度。你要用所有的火柴棍拼成一个正方形。你不能折断任何一根火柴棒,但你可以把它们连在一起,而且每根火柴棒必须使用一次。如果你能使这个正方形,则返回 true ,否则返回 false 。

示例

输入: matchsticks = [1,4,2,3,5,2,3]
输出: true
解释: 能拼成一个边长为2的正方形,每边两根火柴。

题解

首先来分析一下题目具体是什么问题。题目的意思是给定一组长短不同的火柴,利用这些火柴判断是否能拼成一个正方形。正方形就是四条边均相等,这个问题的抽象化表示就是给定一个数组,是否能把数据分成四个子集,每个子集的数的和相同。为了有效说明以上面的示例为例来分析一下该题目的解题方法。
在这里插入图片描述
给定一组可选择的火柴,把这火柴均分到四条边中。显然根据火柴的列表可以确定每一个边的目标值是6。
那么我们先对“可选火柴”中的第一个数据进行分配。显然该数据放在任何一个边中均可。
在这里插入图片描述
如上图所示,把1放在边1中。这里边1当前已有,还需要再填充5。由于如果结果是真的话就一定有一组数据与1一起正好构成一条值为6的边,因此我们只需要逐个的填满每条边就好。这里需要加一个状态值来记录,当前填充边填充了多少。接下来处理第2个数据4。从目前状态来看4是可以放到边1中的,因为1与4的全为5显然小于6。我们先假定把4放到边1中。
在这里插入图片描述

我们发现继续遍历2一直到最后也无法填充满边1。显然把4放到边1中无法找到有效的分配结果。退回原来状态,跳过4。

在这里插入图片描述

2显然也是能放到边1中的,先尝试把2放到边1中。
在这里插入图片描述

当前的3也是能放到边1中,这样边1正好达到目标值6,由于当前边已填充完,开始填充边2了这里当前边已有值就变成0了。
在这里插入图片描述

把5放到边2中。
在这里插入图片描述
但是到这里我们发现我们无法找到能与5一起填充边的值了,显然,从2开始我们的填充。我们开始回退。这里直接回退到。

在这里插入图片描述

再重新开始,重这些操作直到达到如下状态。

在这里插入图片描述

那么我们现在开始整理我们的思路。我们每一步的输入是“可选火柴”与当前已有值,第一步的选择都是不断的尝试,最终是通过“可选火柴”与当前已有值来判断选择该步数值是否能让所有边都填充完。这显然是一个递归过程,每一步的选择都依赖于对剩下状态的递归。

递归函数

  • 输入:“可选火柴”,当前已有值
  • 输出:是否可正好填充完各边
  • 递归过程:
    1 终止条件,当“可选火柴”无数据可选择时终止
    2 等价问题,选择完一个数之后,判断当前状态是否可填充完各边

代码如下:

class Solution:
    def makesquare(self, matchsticks: List[int]) -> bool:
        #边界确定
        bucket_value,div_value=divmod(sum(matchsticks),4)
        matchsticks.sort(reverse=True)
        if div_value:
            return False
        n=len(matchsticks)

        @cache
        def dfs(state,curr_value):
            #终止条件
            if state==0:
                return True
            for i in range(n):
                #判断i位置是否可选
                if state>>i&1:
                    #判断该位置数据是否能与curr_value分到一个释桶
                    if matchsticks[i]+curr_value<=bucket_value:
                        #判断分完i个数后剩下的数据是否满足要求
                        if dfs(state^(1<<i),(curr_value+matchsticks[i])%bucket_value):
                            return True
            return False
        return dfs((1<<n)-1,0)

代码关键部分介绍

bucket_value,div_value=divmod(sum(matchsticks),4)
matchsticks.sort(reverse=True)
if div_value:
    return False

这一块其实就是判断给定的list是否是能被4整除。如果不能整除直接就返回False。
这里使用了状态压缩,这里状态压缩就是用一个数来表示“可选火柴”,怎么表示呢。在遍历可选火柴列表时,已选过的数标记为0表示不能再选了,标记为1的数才可以遍历。如下图所示,只有数据1与数据3的状态表示为1,表示只有剩下这两个数待填充。

在这里插入图片描述

这种表示是一一对应的形式,为了书写方便通常表示倒顺的表示。

在这里插入图片描述

为什么要这么写,比如判断i个位置是否可选择使用state>>i&1,如果要是第一种对应的话就得变成state>>(n-i)&1。后面的state^(1<<i)表示把第i位置的数据为成0表示已选择过不可选了。

计算复杂度

  • 时间复杂度: O ( n × 2 n ) O(n×2^n) O(n×2n) ,其中 n 为 数组 的长度,共有 2 n 2^n 2n个状态,每一个状态进行了 n 次尝试。
  • 空间复杂度: O ( 2 n ) O(2^n) O(2n),其中n 为数组 nums 的长度,主要为状态数组的空间开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值