416分割等和子集
题目:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意: 每个数组中的元素不会超过 100,数组的大小不会超过 200
来源:https://leetcode-cn.com/problems/partition-equal-subset-sum/
法一:动态规划
思路:首先写状态转移方程的时候,如果采用类似312戳气球的方法,即由内到外,由小到大,是难以实现的,原因有二,一是假如题目中的数组元素有6个,则dp[1]即长度为1的子集有6个,dp[2]长度为2的子集有15个,dp[3]长度为3的子集有20个,从dp[1]到dp[2]状态转移的过程中无法实现。二是由于长度不等,难以写成dp数组的形式。说到底是因为,dp[i]定义成了长度为i的子集的和,而不同长度的子集间由于子集的个数不同,难以写出状态转移方程。
由此定义dp[i]为nums中从0到i的子集的和,则dp[i+1]从dp[i]转移过来的时候,由于dp[i]的情况很多,由此确定需要二维数组来记录,二维数组的列用来记录所有的和,如果这个和存在记为True,否则为False。
from typing import List class Solution: def canPartition(self, nums: List[int]) -> bool: n=len(nums) target=sum(nums) if(target%2!=0): return False target//=2 dp=[[False]*(target+1) for _ in range(n)] # 设置边界条件非常重要,将第一列(即和为0的列)设置为True, dp[0][0]=True # 先遍历第一行 for i in range(1,target+1): if(nums[0]==i): dp[0][i]=True break # 遍历其余行 for i in range(1,n): for j in range(target+1): # 如果当前的目标和值大于等于要遍历的值,则将目标和值减去当前值看上一行和为dp[i-1][j-nums[i]]的为True还是False if(j>=nums[i]): dp[i][j]=dp[i-1][j] or (dp[i-1][j-nums[i]]) # 否则说明目标值小于要遍历的值,直接等于上一行的结果 else: dp[i][j]=dp[i-1][j] # 如果最后一列出现True了,说明存在组合成目标值的和.直接返回True # if dp[i][-1] == True: # return True return dp[-1][-1] if __name__ == '__main__': duixiang = Solution() a = duixiang.canPartition([1, 5, 11, 5]) print(a)
来源:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/dong-tai-gui-hua-kong-jian-you-hua-zhu-xing-jie--2/
法二:备忘录方法 非常巧妙
思路:利用ans记录之前的计算结果,每次都和之前的求和后判别是否满足条件。
class Solution: def canPartition(self, nums: List[int]) -> bool: target, remain = divmod(sum(nums), 2) if remain: # 如果不能整除直接返回 return False ans = {0} for i in nums: for j in list(ans): # 循环中会改变ans j += i if j == target: # 提前结束 return True ans.add(j) # 之前的结果加当前数能得到的结果 return False
ttt