题目描述
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
思路
01背包问题,参考:代码随想录
这道题和零钱兑换的顺序相反leetcode-322. 零钱兑换-优快云博客,因为零钱兑换是无限次取,而这个每个数字只能取一次,所有遍历背包时,倒序
1)先判断数组nums总和是不是偶数,如果是奇数,则直接返回false。因为:数组中的元素都是正整数,如果是奇数的话,除以2,是两个小数的和了。不满足题意
2)题目中找到子集的和是sum(nums)//2,即为存在等和自己的。因为target//2,已经找到一半的子集了,另一半就不用找了
3)初始化:如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
4)dp数组遍历顺序,如果使用一维dp数组,外层循环遍历物品,内层循环遍历背包,且需从大到小遍历背包,已防重复。【不可以先遍历背包再遍历物品】
ps:倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
if sum(nums)%2 != 0:
return False
target = sum(nums)//2
dp = [0] * (target+1)
for num in nums:
for j in range(target,num-1,-1):
dp[j] = max(dp[j],dp[j-num]+num)
return dp[-1] == target
if __name__ == '__main__':
s = Solution()
nums = [1, 5, 11, 5]
print(s.canPartition(nums))
记忆点:
0-1背包问题+只找一半的数值
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!