1. 问题描述:
给你一个只包含正整数的非空数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
2. 思路分析:
① 这道题目是一道经典的问题,一开始的时候没有识别出具体的模型,后面看了相关的资料之后发现竟然是零一背包问题的模型,如果识别出了零一背包模型剩下来的就比较好处理了,没有识别出这个模型的话感觉会比较难想出比较好的解决方法。由题可知我们可以将nums数组元素总和的一半看成是背包容量v = s / 2,s为nums数组元素总和,nums[i]表示物品的体积,这里没有价值,我们只需要判断出再背包容量为v的条件下是否可以恰好装满背包即可,这里使用零一背包的模板即可(一维的零一背包问题需要逆序遍历体积),我们一开始的时候需要初始化dp[0] = 1说明在背包容量为0的时候是满足条件的,因为没有涉及到物品的价值所以这里直接使用或运算即可。(在遍历物品的过程中选择拿还是不拿在这个过程中尝试所有可能的组合方案,如果有解那么最终遍历结束之后肯定会标记dp[v] = 1,遍历的过程标记到从第一个物品到当前物品的体积对应的位置为1,结合具体的例子会好理解一点,,遍历第一个物品会将第一个物品的体积对应位置标记为1,遍历第一个物品会将第一个物品与第二个物品体积之和对应的位置标记为1,....第一个物品到第n个物品的体积之和对应的位置标记为1)最终返回dp[v] == 1即可,表示当前背包容量为v的条件下是否可以恰好装满背包。
② 上面的使用两层循环遍历的时间复杂度为O(n ^ 2),我们还可以对其优化一下,主要是对代码等价形式的转化。
3. 代码如下:
from typing import List
class Solution:
def canPartition(self, nums: List[int]) -> bool:
s = 0
# 计算nums所有元素的总和
for x in nums:
s += x
if s % 2: return False
v = s // 2
dp = [0] * (v + 1)
dp[0] = 1
for i in range(len(nums)):
# 逆序遍历背包体积
for j in range(v, nums[i] - 1, -1):
dp[j] |= dp[j - nums[i]]
return dp[v] == 1