给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
- 每个数组中的元素不会超过 100
- 数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
解题思路
这个问题非常的简单,我们通过递归可以很快速的解决。
我们首先要将nums
中的元素加起来的到sum(nums)
,进行取模运算,如果不能整除的话,自然就不可能存在。
如果能整除,我们定义一个新的函数_canPartition(nums, index, result)
,nums
表示传入的数组,index
表示第几个元素,result
表示我们要求解的目标,函数的整体含义是nums
数组中是否存在nums[index]
结尾的和为result
的组合。我们知道nums[index]
结尾的组合的和,可以通过两种途径得到:一种是添加了nums[index]
,另一种是不加上nums[index]
。我们只要将result=sum(nums)/2
作为我们的目标值传入即可。最后代码如下
class Solution:
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
result = sum(nums)
if result%2 != 0:
return False
return self._canPartition(nums, len(nums) - 1, result/2)
def _canPartition(self, nums, index, result):
if result == 0:
return True
if result < 0 or index < 0:
return False
return self._canPartition(nums, index - 1, result) or \
self._canPartition(nums, index - 1, result - nums[index])
同样这个问题我们可以通过记忆化搜索的方法进行优化处理。这个问题和我们之前碰到的问题有一些区别,这里的记忆化搜索建立的存储空间,是一个二维结构,因为我们需要记录两种状态index
和result
class Solution:
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
result = 0
for i in nums:
result += i
if result%2 != 0:
return False
len_nums = len(nums)
mem = [[None]*(result//2 + 1) for _ in range(len_nums)]
return self._canPartition(nums, len(nums) - 1, result//2, mem)
def _canPartition(self, nums, index, result, mem):
if result == 0:
return True
if result < 0 or index < 0:
return False
if mem[index][result] != None:
return mem[index][result]
mem[index][result] = self._canPartition(nums, index - 1, result, mem) or \
self._canPartition(nums, index - 1, result - nums[index], mem)
return mem[index][result]
其实你通过上面的记忆化搜索就可以认识到,这个问题和背包问题很类似。所以我们这里可以通过动态规划的方法快速求解。
class Solution:
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
result = sum(nums)
if result%2 != 0:
return False
len_nums = len(nums)
capacity = result//2
mem = [False]*(capacity + 1)
for i in range(capacity + 1):
mem[i] = (nums[0] == i)
for i in range(1, len_nums):
for j in range(capacity, nums[i]-1, -1):
mem[j] = mem[j] or mem[j - nums[i]]
return mem[capacity]
但是这个问题有一点特殊,我们只要对第一种解法稍加修改就可以非常快的解决这个问题。我们将
self._canPartition(nums, index - 1, result) or self._canPartition(nums, index - 1, result - nums[index])
改为
self._canPartition(nums, index - 1, result - nums[index]) or self._canPartition(nums, index - 1, result)
同时我们添加了nums[index] > result
的剪枝操作。通过这两个优化,你会发现前后所用时间的差距是十分巨大的。
class Solution:
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
result = sum(nums)
if result%2 != 0:
return False
return self._canPartition(nums, len(nums) - 1, result/2)
def _canPartition(self, nums, index, result):
if result == 0:
return True
if result < 0 or index < 0 or nums[index] > result:
return False
return self._canPartition(nums, index - 1, result - nums[index]) or \
self._canPartition(nums, index - 1, result)
为什么?什么原因造成了时间消耗前后差距这么大?实际上这和测试数据的分布有关,测试数据主要是从小到大分布的话,那么就会很快,所以我们上述代码还可以进行优化,就是在代码初添加一个排序操作。另外为什么将or
前后换了个位置变化会很大呢?也是数据分布的问题,我们并没有那么多正好是result
的数据(不用拆分,即是结果),大部分数据多是需要拆分的。
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!