代码随想录第31天:动态规划4

一、目标和(Leetcode 494)

假设加法的总和为x,那么减法对应的总和就是sum - x。

所以我们要求的是 x - (sum - x) = target

x = (target + sum) / 2

此时问题就转化为,用nums装满容量为x的背包,有几种方法。

1.确定dp数组以及下标的含义:装满容量为 j 的背包,有dp[ j ] 种方法。
2. 确定递推公式:dp[j] = dp[j] + dp[j - nums[i]] ,即:dp[j] += dp[j - nums[i]]
3. dp数组如何初始化:dp[ 0 ] = 1

4.遍历物品放在外循环,遍历背包在内循环,且内循环倒序(为了保证物品只使用一次)。

class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        total = sum(nums)  # 计算nums数组中所有元素的总和
        # 如果目标值的绝对值大于数组的总和,或者 (target + total) 是奇数,则没有有效方案
        if abs(target) > total or (target + total) % 2 == 1:
            return 0  # 没有可行方案

        # 计算目标背包容量,转换成子集和问题
        bagsize = (target + total) // 2  # 目标和的背包容量

        # 动态规划数组dp,dp[i]表示和为i的方案数
        dp = [0] * (bagsize + 1)  # 初始化dp数组,大小为bagsize+1
        dp[0] = 1  # 当目标和为0时,只有一种方案:不选择任何数字

        # 遍历nums数组中的每个数字
        for num in nums:
            # 从bagsize到num的倒序遍历,避免重复计算
            for j in range(bagsize, num - 1, -1):
                # 更新dp[j],即累加当前数字num参与的组合方式
                dp[j] += dp[j - num]

        # 返回dp[bagsize],即达到目标和bagsize的方案数
        return dp[bagsize]

二、一和零(Leetcode 474)

1.确定dp数组以及下标的含义:dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
2.确定递推公式:

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。

然后我们在遍历的过程中,取dp[i][j]的最大值。

所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。

3.dp数组如何初始化:

因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

4.遍历顺序:外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历
class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        # 创建一个二维动态规划数组 dp,dp[i][j] 表示在容量 i 个 0 和 j 个 1 时,能够形成的最大子集数量
        dp = [[0] * (n + 1) for _ in range(m + 1)]  # 初始化二维数组,所有值都为0
        
        # 遍历所有的字符串
        for s in strs:
            zeroNum = s.count('0')  # 统计当前字符串中0的个数
            oneNum = len(s) - zeroNum  # 统计当前字符串中1的个数
            
            # 从背包容量 m 和 n 开始倒序遍历,防止重复计算
            for i in range(m, zeroNum - 1, -1):  # 遍历0的容量
                for j in range(n, oneNum - 1, -1):  # 遍历1的容量
                    # 状态转移方程:选择当前字符串后,更新dp数组
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)
        
        # 最终结果是 dp[m][n],即在容量 m 和 n 下能选择的最大字符串数量
        return dp[m][n]

三、完全背包理论基础(Kamacoder 52)

1. 确定dp数组以及下标的含义:
dp[i][j] 表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少。
2. 确定递推公式:
  • 不放物品i:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]。

  • 放物品i:背包空出物品i的容量后,背包容量为j - weight[i],dp[i][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

递推公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);

(注意,完全背包二维dp数组 和 01背包二维dp数组 递推公式的区别,01背包中是 dp[i - 1][j - weight[i]] + value[i])

3. dp数组如何初始化:

从状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 可以看出 i 是由 i-1 推导出来。

dp[0][j],即:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

j >= weight[0]时,dp[0][j] 如果能放下weight[0]的话,就一直装,每一种物品有无限个。

4. 确定遍历顺序:

01背包二维DP数组,先遍历物品还是先遍历背包都是可以的。

因为两种遍历顺序,对于二维dp数组来说,递推公式所需要的值,二维dp数组里对应的位置都有。

对于完全背包既可以先遍历物品再遍历背包,也可以先遍历背包再遍历物品

def knapsack(n, bag_weight, weight, value):
    # 初始化二维DP数组,dp[i][j]表示使用前i种物品(索引0到i-1),容量为j时能获得的最大价值
    dp = [[0] * (bag_weight + 1) for _ in range(n)]
    
    # 初始化第一行(i=0,仅考虑第一种物品)
    # 对于容量j >= weight[0],可以多次放入第一种物品,价值为(j // weight[0]) * value[0]
    for j in range(weight[0], bag_weight + 1):
        dp[0][j] = (j // weight[0]) * value[0]  # 计算能放入第一种物品的最大次数
    
    # 动态规划主循环
    for i in range(1, n):
        # 遍历所有可能的背包容量,从0到bag_weight
        for j in range(bag_weight + 1):
            # 如果当前物品的重量大于当前容量j,无法放入该物品
            # 直接继承上一行的结果(不选当前物品)
            if j < weight[i]:
                dp[i][j] = dp[i - 1][j]
            # 否则,选择以下两种情况的最大值:
            # 1. 不选当前物品:dp[i-1][j]
            # 2. 选当前物品(可重复):dp[i][j - weight[i]] + value[i]
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])
    
    # 返回使用所有物品且背包容量为bag_weight时能获得的最大价值
    return dp[n - 1][bag_weight]

# 读取输入:物品种类数量n和背包容量bag_weight
n, bag_weight = map(int, input().split())

# 初始化存储物品重量和价值的列表
weight = []
value = []

# 读取n行输入,每行包含一种物品的重量和价值
for _ in range(n):
    w, v = map(int, input().split())
    weight.append(w)  # 将物品重量添加到weight列表
    value.append(v)   # 将物品价值添加到value列表

# 调用knapsack函数并输出最大价值
print(knapsack(n, bag_weight, weight, value))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值