本题是01背包的变体
1、题目描述:
2、题解:
动态规划:
1、状态定义;
2、状态转移方程;
3、初始化;
4、输出;
5、思考状态压缩。
可以用递归去求,但是会存在重叠子问题,加个备忘录可以解决重复问题。
需要先判断数组元素之和target是否为偶数,如果为奇数则不能分割满足题目要求的两个数组;否则令target为target自身的一半。(这是和背包问题的区别)
转化为01背包问题,其中数组的长度n=len(nums)为物品数,target为背包的容量。我们用打表格法去做题。
状态定义:dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。dp[i][j] = x 表示,对于前 i 个物品,当前背包的容量为 j 时,若 x 为 true,则说明可以恰好将背包装满,若 x 为 false,则说明不能恰好将背包装满。
状态转移方程:很多时候,状态转移方程思考的角度是“分类讨论”,对于“0-1 背包问题”而言就是“当前考虑到的数字选与不选”dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]。
解释:如果不把 nums[i] 算入子集,或者说你不把这第 i 个物品装入背包,那么是否能够恰好装满背包,取决于上一个状态 dp[i-1][j],继承之前的结果。
如果把 nums[i] 算入子集,或者说你把这第 i 个物品装入了背包,那么是否能够恰好装满背包,取决于状态 dp[i - 1][j-nums[i-1]]。
base case 就是 dp[…][0] = true 和 dp[0][…] = false,因为背包没有空间的时候,就相当于装满了,而当没有物品可选择的时候,肯定没办法装满背包
伪代码如下
#初始化 ,n个物品,背包的总容量为target
dp[n+1][target+1] 全为False
dp[:][0] = True
for i in [1...n]:
for j in [1...target]:
dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i-1]]
return dp[-1][-1]
处理一些边界问题,python代码如下:
class Solution:
def canPartition(self, nums: List[int]) -> bool:
#动态规划,01背包
n = len(nums)
target = sum(nums)
if target % 2 != 0:
return False
target //= 2
dp = [[False] * (target + 1) for _ in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1,n+1):
for j in range(1,target+1):
if j >= nums[i-1]:
dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i-1]]
else:
dp[i][j] = dp[i-1][j]
return dp[-1][-1]
状态压缩:
上面的代码时间和空间复杂度都是O(N*len(target)),我们可以对空间做优化
伪代码:
#初始化 ,n个物品,背包的总容量为target
dp[target+1] 全为False
dp[0] = True
for i in [1...n]:
for j in [target...1]:
dp[j] = dp[j] or dp[j-nums[i-1]]
return dp[-1]
做一下边界处理,python代码如下:
class Solution:
def canPartition(self, nums: List[int]) -> bool:
#动态规划,01背包
n = len(nums)
target = sum(nums)
if target % 2 != 0:
return False
target //= 2
dp = [False] * (target + 1)
dp[0] = True
for i in range(1,n+1):
for j in range(target,0,-1):
if j >= nums[i-1]:
dp[j] = dpj] or dp[j-nums[i-1]]
return dp[-1]
此时状态复杂度为O(target)
另外可以参考之前的博客动态规划入门:01背包问题,提供模板和思路,简单易懂好上手(同时可在相应网站上做练习)添加链接描述
也可以同步做一下LeetCode 474、494