473. 火柴拼正方形
题目描述
给一个数组nums,nums中元素代表火柴棒的长度。
给的要求是火柴棒可以连接,不能折断,每根必须用一次,问能不能连成正方形?
基本分析
f1-状态压缩+动态规划
- 火柴数组长度不超过15暗示什么?可以用二进制数s表示数组中索引对应的火柴的使用情况
- 火柴数组什么情况可以提前排除?(1)长度总和不是4的倍数;(2)有某根长度>边长
- 火柴使用情况用什么表示?二进制的数字state
- dp的状态怎么定义?利用f[state]表示正方形未被放满的边的当前长度
- 状态的初始化和最终结果怎么定义?初始化f[0]=0表示不放火柴时,当前长度是0;最终结果是f[(1<<n)-1],表示都用的时候,未被放满边的长度,如果要满足条件,需要f[-1]==0
- 状态怎么进行转移?对当前的状态s,找到包含1的所有位置。以遍历到i为例,对应的火柴长度是v。考虑转移时,去掉i位置放置火柴的情况,对应状态为s1,当f[s1]+v<=边长时,表示可以转移f[s] = (f[s1]+v)%line, 此时可以不再继续遍历i,可以break,直接去讨论下一个状态;否则当f[s1]+v > 边长line时,不能进行转移,只能考虑其他别的放置情况。
f2-回溯
- 排除简单不满足的情况?和上面一样
- 怎么在遍历的是减少搜索量?对火柴从大到小排序
- 怎么定义辅助的数组表示状态?定义了一个数组edges,其中每个元素edges[i]表示当前i边的长度
- dfs的参数需要什么?定义idx表示处理到了哪个索引,如果idx=火柴长度,返回True,表示到栈尾
- 回溯的中间逻辑是什么?对idx没有到达最终长度时,对饮的值是v。 遍历每个边0-4,先把值加到对应的边,新长度是edges[i]+v,看新长度是不是满足要求且后面也满足要求,如果是,可以返回True,需要在i边的放置,分析放到下一个边的情况
- 怎么开始遍历?dfs(0),表示从索引0开始。
代码
f1-状态压缩+动态规划
class Solution: def makesquare(self, matchsticks: List[int]) -> bool: n = len(matchsticks) total = sum(matchsticks) if total % 4 != 0: return False line = total // 4 if any(num > line for num in matchsticks): return False final = 1<<n f = [-1] * final f[0] = 0 for s in range(1, final): for i, k in enumerate(matchsticks): if s & (1<<i) == 0: continue s1 = s & (~(1<<i)) if f[s1] >=0 and f[s1] + k <=line: f[s] = (f[s1] + k) % line break return f[-1]==0
f2-回溯
class Solution: def makesquare(self, matchsticks: List[int]) -> bool: n = len(matchsticks) total = sum(matchsticks) if total % 4 != 0: return False line = total / 4 if any(num > line for num in matchsticks): return False matchsticks.sort(reverse=True) edges = [0] *4 def dfs(idx): if idx == n: return True for i in range(4): edges[i] += matchsticks[idx] if edges[i] <= line and dfs(idx+1): return True edges[i] -= matchsticks[idx] return False return dfs(0)
复杂度
f1-状态压缩+动态规划
时间:, n是火柴数组的长度,有个状态,每个状态内在遍历火柴需要
空间: ,保存dp数组需要的空间
f2-回溯
时间:
空间:,递归栈需要
总结
f1-状态压缩+动态规划
- 能看出来火柴数组长度不大于15,考虑用二进制来表示对应索引火柴的使用情况,进行状态压缩
- 能利用简单规则排除明显不满足的情况
- 定义dp状态时,定义的放置方式是依次放入,一条边满后再放另一条。对应的dp状态定义为f[s]s状态对应的,正方形未放满边的当前长度
- 状态转移时先遍历每个状态,再遍历状态内1对应的i(在代码中是枚举i,不满足s % (1<<i)==1时跳过);下面需要去找i处不放的状态s1,具体实现是s1,s & (~(1<<i))。这里s1一定能转移到s吗?不一定。(1)如果能转移,需要满足f[s1]>=0(代表s1合法),f[s1]+v <=line,表示s的放法不会超过边长,要吗不够继续放,要么刚好满。(2)不能转移有哪些情况?可能是遍历火柴后,不存在合法的s1;也可能是f[s1]+v > line, 不能进行转移
- 对某个s,找到合适的s1转以后,为啥直接break?已经找到结果了,这个结果也不会再变,直接去找下一个state就行了。
f2-回溯
- 简单排除不满足的情况
- 对火柴排序,减少搜索空间
- 定义edges辅助数组,表示每个边当前的长度,用来配合dfs
- dfs时,需要的参数是idx。退出的出口是idx等于数组长;对每个idx遍历在每个边的放置情况;每个边内的逻辑是(1)放+(2)判断+(3)撤回
- 调用时,从索引0开始考虑,就是dfs(0)
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现