Question
Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.给定一个非空正整数的列表,将其划分为两个子集,判断是否存在两个子集的总和相等的情况
Example
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.
Solution
第一种非常容易想到的是DFS+记忆化。因为从源列表中划分两个总和相等的子集,那么只要任意个元素的总和达到源列表总和的一半即可(target = sum(originSetList) / 2),所以我们通过深度优先搜索遍历整个列表,同时为了防止重复计算,我们保存中间变量
class Solution(object): def canPartition(self, nums): """ :type nums: List[int] :rtype: bool """ s = sum(nums) # 如果总和不是偶数则不可能分成两个总和相等的子集 if s % 2 != 0: return False self.memory = {} return self.solve(nums, int(s / 2)) def solve(self, nums, target): # 如果保存过当前情况,直接返回 if target in self.memory: return self.memory[target] # 如果已经遍历完源列表或者已遍历的元素总和已经大于target则代表不存在这样的子集 # target < 0比较重要,这相当于剪枝的过程,如果不添加这行,运行总时间需要3000多毫秒,而添加后只需要60多毫秒 if len(nums) == 0 or target < 0: return False # 如果当前子集总和刚好等于target则代表已找到划分的两个子集 if target == 0: return True # 回溯过程 for index, value in enumerate(nums): # 保存返回的值 if self.solve(nums[:index] + nums[index + 1:], target - nums[index]): self.memory[target] = True return True else: self.memory[target] = False # 如果搜索完所有情况仍然没有找到那么返回False代表不存在这种情况 return False
这道题还可以用动态规划做,虽然耗费的时间比剪枝过的dfs长。思路如下:如果target是由任意个元素组成,那么我们就可以划分两个总和相等的子集。而如果任意个元素总和i是由任意个元素组成的,那么加上列表元素i + nums[j]仍然由任意个元素组成的。假设dp[i]代表i是否是任意个元素的总和,由此我们得到递推式:d[i] = d[i - nums[j]] or d[i],在遍历列表nums的过程中,我们需要更新[nums[j], target]之间的值
class Solution(object): def canPartition(self, nums): """ :type nums: List[int] :rtype: bool """ s = sum(nums) if s % 2 != 0: return False target = int(s / 2) # 默认dp[0]为True以保证d[num[j]]为True dp = [True] + [False] * target for n in nums: # 这里的更新需要从target到n,否则会出现更新错误 for index in range(target, n - 1, -1): dp[index] = dp[index] or dp[index - n] return dp[target]