代码随想录算法训练营第三十六天| DP4— 1049. 最后一块石头的重量 II,494. 目标和

继续还债。1049. 最后一块石头的重量 II494. 目标和474. 一和零。01背包会了,但是应用不会,就是单独摆明给我一道类似的题目做起来会很快,但是套到某些场景里面就很难看出来怎么用01背包去做。

1049. 最后一块石头的重量 II

1049. 最后一块石头的重量 II - 力扣(LeetCode)

要是不知道这是DP题目,我是绝对想不出来是用DP的。如果不是说这道题和昨天那道分割字数和思路类似的,我也想不出来这是01背包的。知道了这两点,开始面向方法编程,硬往这个方向靠,才想出来题目可以简化为,把所有石头分成重量尽量类似的两堆然后相撞,最后剩下就是最少,如果能分成等重,那就能为0。所以代码前半部分和之前题目类似,最后需要多一步求剩余重量,依旧是倒着遍历,选重量从大到小,最后剩下的重量就少,当为True的时候,返回就行了。return0是一个兜底的情况,实际不会发生。

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        total = sum(stones)
        target = total // 2
        dp = [False] * (target + 1)
        dp[0] = True

        for stone in stones:
            for j in range(target, stone-1 , -1):
                dp[j] = dp[j] or dp[j - stone]

        for i in range(target, -1, -1):
            if dp[i]:
                # 总重量减去两倍的最接近总重量一半的重量
                return total - 2 * i

        return 0

494. 目标和

494. 目标和 - 力扣(LeetCode)

勉强看着题解理解懂的一道题,最主要的是自己想破头也不可能想出来这是01背包的问题。首先是要经过一个公式推导,得出类似上题石头碰撞的表达式,就是推我们的target_sum 等于所有值的和+目标值的和然后除以二,让我们通过背包判断出来这样的方案有多少个。

dp的含义是类似于背包的,dp[i][j] = 从前 i+1 个数(即 nums[0..i])中,选若干个数,使得它们的和为 j 的方案数量

创建数组和之前一样,然后初始化第一行,dp[0][0] = 1代表空集就能凑出0,所以方案数为1。然后判断如果第一个数就不超过目标和,那么我们可以选择它一个人单独构成 nums[0] 这个和,方案数为 1

之后是特殊处理0这个值,因为0在数组中可选可不选,但是最终还是会影响方案数,所以需要特殊考虑。遍历所有数字,算出有k个0,那么方案数就是2的k次方。最后状态转移方程类似于01背包的dp[i][j] = dp[i-1][j] + dp[i-1][j - nums[i]]。

(之后还得重新理解一下这道题,太难了)

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        """
        先进行一个数学推导,P为正数数组和,N为负数数组和,那么P+N = total, P-N = target
        P + N + P - N = total + target
        P = (total + target) // 2
        """
        total_sum = sum(nums)  
        if abs(target) > total_sum:  # 全是正数也没有target大
            return 0  
        if (target + total_sum) % 2 == 1:  # 保证能被整除
            return 0 
        target_sum = (target + total_sum) // 2  

        # 创建二维动态规划数组,行表示选取的元素数量,列表示累加和
        dp = [[0] * (target_sum + 1) for _ in range(len(nums))]

        # 初始化状态
        dp[0][0] = 1
        if nums[0] <= target_sum:
            dp[0][nums[0]] = 1
            
        numZero = 0
        for i in range(len(nums)):
            if nums[i] == 0:
                numZero += 1
            dp[i][0] = int(math.pow(2, numZero))

        for i in range(1, len(nums)):
            for j in range(target_sum + 1):
                dp[i][j] = dp[i - 1][j]  # 不选取当前元素
                if j >= nums[i]:
                    dp[i][j] += dp[i - 1][j - nums[i]]  # 选取当前元素

        return dp[len(nums)-1][target_sum] 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值