Leetcode 416:分割等和子集(最详细的解法!!!)

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/82899422

给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

  1. 每个数组中的元素不会超过 100
  2. 数组的大小不会超过 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])

同样这个问题我们可以通过记忆化搜索的方法进行优化处理。这个问题和我们之前碰到的问题有一些区别,这里的记忆化搜索建立的存储空间,是一个二维结构,因为我们需要记录两种状态indexresult

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

如有问题,希望大家指出!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/82899422