代码随想录 Day 41 | 【第九章 动态规划 part 04】1049. 最后一块石头的重量 II、494. 目标和、474.一和零

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

本题就和 昨天的 416. 分割等和子集 很像了,可以尝试先自己思考做一做。

1. 具体思路

将石头分为近似大小和重量的两堆,最后两两相撞最多只剩下一块石头。

(1)dp数组的含义:装满容量为j的背包它的最大重量为dp[j]。

(2)递推公式:本题中石头的重量就是石头的价值,递推公式为dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])

(3)dp数组初始化:dp[0]=0,容量为0所以重量也为0。非0下标也初始化为0,因为必须为非负,且后续需要比较最大值,所以只能初始化为0。此处需要注意定义dp数组的大小,由于题目描述为1 <= stones.length <= 30且1 <= stones[i] <= 100,所以30*100/2=1500,定义dp数组大小为1500即可。

(4)遍历顺序:第一层for循环遍历的是每个石头;第二层遍历的是背包,并且需要从大到小进行遍历,保证每个物品只放一次;target就是sum/2,凑这样大的一个背包,并且第二层需要保证当前容量大于石头。

(5)打印dp数组。

2. 代码实现

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        dp = [0] * 15001
        total_sum = sum(stones)
        target = total_sum // 2
        for i in stones:
            for j in range(target, i-1, -1):
                dp[j] = max(dp[j], dp[j-i]+i)
        return total_sum - dp[target]-dp[target]

二、494. 目标和

大家重点理解 递推公式:dp[j] += dp[j - nums[i]],这个公式后面的提问 我们还会用到。

1. 具体思路

(1)定义dp数组:装满容量为 j 的背包有dp[ j ]种方法。

(2)递归公式:dp[ j ] += dp[ j-nums[i]]

(3)dp数组初始化:dp[0] = 1,不可以初始化为0,因为递推公式会一直是0。非0下标初始化为0即可,因为需要进行累加操作,如果初始为非0 ,那么会影响递归公式的推导。

(4)遍历顺序:第一层遍历物品,第二层遍历背包容量(倒序),因为每个背包只能使用一次,所以是倒序。

(5)打印dp数组。

2. 代码实现

  1. 计算总和 total_sum
    • 计算数组 nums 的总和是正确的第一步,用于后续判断。
  2. 提前返回0的情况
    • 如果 abs(target) 大于 total_sum,则不可能通过选取数组中的元素来达到目标值(考虑正负情况)。
    • 如果 (target + total_sum) % 2 == 1,意味着目标和为奇数,而数组中的元素总和为偶数(或反之),那么也无法通过选取元素达到目标值(因为选取元素相当于做加减运算,无法改变总和的奇偶性)。
  3. 目标和 target_sum
    • 这里的 target_sum 实际上是将问题转化为背包问题中的“不超过某个目标和的最大值问题”。因为 target 可以是负数,所以通过 target + total_sum 将其转化为非负问题,然后除以2得到我们需要达到的一半和(因为我们可以选择一部分元素使其和为某个值,剩下的元素和就是 total_sum 减去这个值,两者和等于 total_sum + target,即目标和为 (target + total_sum) // 2)。
  4. 动态规划数组 dp
    • dp[j] 表示选取若干元素和为 j 的方案数。
    • 初始化 dp[0] = 1 是正确的,表示和为0(即不选取任何元素)的方案数为1。
  5. 状态转移方程
    • dp[j] += dp[j - num] 表示在已经考虑了和为 j-num 的方案数基础上,加上当前元素 num 可以得到和为 j 的方案数。
    • 注意循环的范围,这里应该是 range(target_sum, num - 1, -1) 改为 range(target_sum, num - 1, -1) 实际上是有问题的,因为当 j = num - 1 时,dp[j - num] 会访问 dp[-1],这是越界的。正确的范围应该是 range(target_sum, num - 1, -1) 改为 range(target_sum, num - 1, -1) 的上界调整为 num(包含 num),即 range(target_sum, num - 1, -1) 改为 range(target_sum, num, -1)。但更常见的写法是从 target_sum 到 num(不包含 target_sum - num < 0 的情况),即 range(max(target_sum - num, 0), target_sum + 1)。不过由于 j 从 target_sum 开始递减,且我们关心的是 dp[j] 的更新不依赖于未来的 dp[j+k](k>0),所以只需要保证不越界即可。
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        sum_total = sum(nums)
        target_sum = (sum_total + target) // 2
        if abs(target) > sum_total:
            return 0
        if (target + sum_total) % 2 == 1:
            return 0
        dp = [0] * (target_sum+1)
        dp[0] = 1
        for i in nums:
            for j in range(target_sum, i-1, -1):
                dp[j] += dp[j-i]
        return dp[target_sum]

三、474.一和零

通过这道题目,大家先粗略了解, 01背包,完全背包,多重背包的区别,不过不用细扣,因为后面 对于 完全背包,多重背包 还有单独讲解。

1. 具体思路

首先这道题属于0-1背包的题目,而不是多重背包,只限制了一个背包最多装多少种物品。

(1)定义dp数组及初始化:初始化一个二维动态规划数组 dp,大小为 (m+1) x (n+1),dp[i][j] 表示在至多有 i 个 '0' 和 j 个 '1' 的条件下,可以选择的最大字符串数量;
(2)遍历字符串数组 strs 中的每一个字符串 s,统计字符串 s 中 '0' 的数量,统计字符串 s 中 '1' 的数量;
(3)遍历顺序这里的 range 条件应该包含 zero 和 one,以确保考虑到当前字符串 s,但是从后向前遍历(逆序)是为了避免重复使用同一个字符串 s;
(4)dp数组递推公式:dp[i][j] = max(dp[i][j], dp[i-zero][j-one] + 1)
(5)打印dp数组:返回最终结果,在至多有 m 个 '0' 和 n 个 '1' 的条件下,可以选择的最大字符串数量,return dp[m][n]。

2. 代码实现

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        dp = [[0]*(n+1) for _ in range(m+1)]
        for s in strs:
            one = s.count('1')
            zero = s.count('0')
            for i in range(m, zero-1, -1):
                for j in range(n, one-1, -1):
                    dp[i][j] = max(dp[i][j], dp[i-zero][j-one]+1)
        return dp[m][n]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值