算法训练 Day 42

LeetCode 416. 分割等和子集

题目链接:416. 分割等和子集

思路:这道题乍一看可以用暴力解法做,但时间复杂度会很高,所以做这道题需要先进行一个等价转换,将题目要求转换为:是否可以从输入数组中挑出一些正整数,使得这些数的和等于整个数组元素的和的一半。(如果数组元素和为奇数,可以直接返回false。)并且本题的每一个元素只有不选择和选择一次两种方式,因此联想到使用01背包问题。

  1. 确定dp数组及其下标的定义
    01背包问题可以使用二维数组,也可以使用一维数组,下面用两种语言来分别实现一维dp数组和二维dp数组的代码逻辑,因为dp数组的定义会直接影响到之后的递推公式等绝大部分逻辑。
    如果定义dp数组为二维数组的话,那 d p [ i ] [ j ] dp[i][j] dp[i][j]就表示从数组的 [0, i] 这个区间内挑选一些正整数,每个数只能用一次,使得这些数的和最大。 d p [ i ] [ j ] dp[i][j] dp[i][j]的列可以设为背包容量(本题最大容量为整个数组元素和的一半), d p [ i ] [ j ] dp[i][j] dp[i][j]的行可以设为物品数量(本题为数组中各个索引位置的元素值)。
    如果定义dp数组为一维数组的话,那 d p [ j ] dp[j] dp[j]就表示容量为j的背包,所背的物品价值(本题即为元素之和)最大为dp[j]。
  2. 确定递推公式
    第i件物品(本题为第i个数)只有两种选择:选它或者不选它。
    1. 选第 i 件物品
      如果能选且选上第 i 件物品,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] + n u m s [ i ] dp[i][j] = dp[i-1][j-nums[i]]+nums[i] dp[i][j]=dp[i1][jnums[i]]+nums[i]
    2. 不选第 i 件物品
      如果 j < nums[i],则 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]
  3. dp数组的初始化
    因为dp数组的更新顺序是从i-1更新到i(从上至下),从j-1更新到j(从左至右),所以二维dp数组初始化的时候需要初始化左边界和上边界。左边界 d p [ i ] [ 0 ] dp[i][0] dp[i][0]的含义相当于背包容量为0时能达到的最大元素和,根据实际情况也为0,所以左边界全部初始化为0。上边界 d p [ 0 ] [ j ] dp[0][j] dp[0][j]的含义相当于背包容量为j时,能不能放入第一个元素nums[0],如果能的话就填入nums[0],如果不能的话则为0,也就是说,上边界从 d p [ 0 ] [ n u m s [ 0 ] ] dp[0][nums[0]] dp[0][nums[0]]及其之后的位置全部填入nums[0]
  4. 确定遍历顺序
    本题,先遍历物品再遍历背包,或者先遍历背包再遍历物品都可以,但按照一般的思维逻辑,先遍历物品,再每个物品里遍历背包的重量。
  5. 返回结果
    返回 d p [ l e n ( n u m s ) − 1 ] [ t a r g e t ] = = t a r g e t dp[len(nums)-1][target]==target dp[len(nums)1][target]==target 的结果。

Python版本(二维dp数组):

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        target = sum(nums)
        if target % 2 == 1:
            return False
        else:
            target //= 2

        dp = [[0] * (target+1) for _ in range(len(nums))]
        for i in range(nums[0], target+1):
            dp[0][i] = nums[0]

        for i in range(1,len(nums)):
            for j in range(target+1):
                if j < nums[i]:   # 容量有限,无法选择第i个数字nums[i]
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i])

        return dp[len(nums)-1][target]==target

时间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(nSum/2),空间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(nSum/2)

go版本(一维dp数组):

func canPartition(nums []int) bool {
    sum := 0
    for i:=0; i<len(nums); i++ {
        sum += nums[i]
    }
    if (sum % 2 == 1) {
        return false
    }
    target := sum/2
    dp := make([]int, target+1)
    for i:=0; i<len(nums); i++ {
        for j:=target; j>=nums[i]; j-- {
            if dp[j] < dp[j - nums[i]] + nums[i] {
                dp[j] = dp[j - nums[i]] + nums[i]
            }
        }
    }
    return dp[target]==target
}

时间复杂度: O ( n ∗ S u m / 2 ) O(n*Sum/2) O(nSum/2),空间复杂度: O ( S u m / 2 ) O(Sum/2) O(Sum/2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值