day30-数据结构力扣

动态规划类-背包问题

重点是0-1背包和完全背包,其他类型都是竞赛级别的

背包问题的定义

背包问题是一类经典的组合优化问题,核心是在有限容量的约束下,从一组物品中选择子集以最大化或最小化目标(如价值或成本)。

常见变种

  • 多重背包:每种物品有数量限制。

  • 分组背包:物品分组,每组只能选一个。

  • 多维背包:容量限制涉及多个维度(如重量和体积)。

0-1背包问题

0-1背包问题是最基础的背包问题形式。给定一组物品,每个物品有固定的重量和价值,背包有容量限制。每个物品只能选择装入(1)或不装入(0),目标是在不超过背包容量的前提下,最大化背包中物品的总价值。

特点

  • 每个物品只能选择一次。

  • 通常用动态规划解决,状态转移方程为:

  • 其中 (dpij) 表示前 (i) 个物品在容量 (j) 下的最大价值, 分别为第 (i) 个物品的重量和价值。

完全背包问题

完全背包问题与0-1背包类似,但允许每种物品无限次选择。即物品可以重复装入背包,只要总重量不超过容量限制。

特点

  • 物品可重复选取。

  • 动态规划的状态转移方程为:

    注意第二项为 ,表示可重复选择当前物品。

背包问题二维

1.dpij

含义【0,i】下标的物品任取,放入容量为j的背包里

2.递推公式

放物品i,或者不放物品i

不管放不放物品i,【0,i-1】物品都是任取

假设不放物品i:dp【i-1】【j】

假设放物品i:dp【i-1】【j-weight【i】】+value【i】

dp【i】【j】=max(dp【i-1】【j】,dp【i-1】【j-weight【i】】+value【i】)

3.初始化

其他位置初始化什么都可以,因为后面会被覆盖掉

4.遍历顺序

都可以

python 复制代码
# 1. 输入:n是物品数量,bagweight是背包最大承重
n, bagweight = map(int, input().split())

# 2. 输入:每个物品的重量数组
weight = list(map(int, input().split()))
# 输入:每个物品的价值数组
value = list(map(int, input().split()))

# 3. 创建二维DP数组
# dp[i][j]:表示 前i个物品里选,背包容量为j 时的最大价值
# 维度:n行(物品),bagweight+1列(容量0~bagweight),全部初始化为0
dp = [[0] * (bagweight + 1) for _ in range(n)]

# 4. 初始化第一行(只有第0个物品时)
# 背包容量j >= 物品0重量时,才能放下,价值=value[0]
for j in range(weight[0], bagweight + 1):
    dp[0][j] = value[0]

# 5. 填充DP表(从第1个物品开始遍历)
for i in range(1, n):
    # 遍历所有背包容量
    for j in range(bagweight + 1):
        # 如果当前容量j 装不下 第i个物品
        if j < weight[i]:
            # 只能不选这个物品 → 等于上一行的结果
            dp[i][j] = dp[i - 1][j]
        # 装得下:两种选择取最大
        else:
            # 不选i:dp[i-1][j]
            # 选i:dp[i-1][j - weight[i]] + value[i]
            dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

# 6. 答案:n个物品,容量bagweight时的最大价值
print(dp[n - 1][bagweight])

背包问题一维

动态规划:01背包理论基础(滚动数组) | 动态规划 | 滚动数组 | 01背包 | 代码随想录

在使用二维数组的时候,递推公式:dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei);

其实可以发现如果把dpi - 1那一层拷贝到dpi上,表达式完全可以是:dpij = max(dpij, dpij - weight\[i] + valuei);

与其把dpi - 1这一层拷贝到dpi上,不如只用一个一维数组了,只用dpj(一维数组,也可以理解是一个滚动数组)。

这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

1.dp数组的含义

dpj表示:容量为j的背包,所背的物品价值可以最大为dpj

2.递推公式

二维dp数组的递推公式为: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

两种状态:放或者不放

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

3.初始化

我们要得到最大值,初始化的时候取一个小值,后面计算可以覆盖掉

dp[0]=0

4.遍历顺序

难以理解,我看不懂

二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。

为什么呢?倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次

python 复制代码
n, bagweight = map(int, input().split())
weight = list(map(int, input().split()))
value = list(map(int, input().split()))

dp = [0] * (bagweight + 1)  # 创建一个动态规划数组dp,初始值为0

dp[0] = 0  # 初始化dp[0] = 0,背包容量为0,价值最大为0

for i in range(n):  # 应该先遍历物品,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品
    for j in range(bagweight, weight[i]-1, -1):  # 倒序遍历背包容量是为了保证物品i只被放入一次
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

print(dp[bagweight])

416. 分割等和子集

题目链接416. 分割等和子集 - 力扣(LeetCode)

思路

  • 数组总和为 sum_total,若能分成两个和相等子集,总和必须是偶数,目标子集和:target=sum_total÷2

  • 问题转化为:01 背包问题从数组中选若干数字,总和恰好等于 target

  • DP 定义:dp[j] = 背包容量为 j 时,能否凑出和为 j

关键细节

  1. 奇数直接返回 False:无法平分

  2. 倒序遍历背包容量:01 背包经典写法,保证每个数字只选 1 次

  3. 状态转移:dpj=dpj∥dpj−num

  • 不选当前数字:维持原本能否凑出

  • 选当前数字:看 j-num 是否能凑出

提交

python 复制代码
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2 != 0:
            return False
        n = len(nums)
        target = total // 2
        
        # dp[i][j]:前i个数,能否凑出和j
        dp = [[False]*(target+1) for _ in range(n)]
        
        # 初始化第一个数
        if nums[0] <= target:
            dp[0][nums[0]] = True
        
        for i in range(1, n):
            for j in range(target + 1):
                # 不选第i个数
                dp[i][j] = dp[i-1][j]
                # 选第i个数
                if j >= nums[i]:
                    dp[i][j] = dp[i][j] or dp[i-1][j - nums[i]]
        
        return dp[-1][target]
相关推荐
大圣编程15 小时前
面向对象深度理解
java·开发语言·算法
爱喝水的鱼丶15 小时前
SAP-ABAP:SAP 简单报表输出开发系列(共6篇) 第四篇:SAP 报表异常处理机制:数据校验与消息提示规范落地
开发语言·数据库·学习·算法·sap·abap
wabs66615 小时前
关于贪心算法【划分字母区间】的问题总结(C++语法)
算法·贪心算法
啦啦啦啦啦zzzz16 小时前
数据结构:二叉树的线索化
数据结构·算法
2401_8724187816 小时前
算法入门:并查集(Disjoint Set / Union-Find):连通性问题的利器
算法
luj_176816 小时前
R语言生态优势与学习曲线分析
c语言·开发语言·网络·经验分享·算法
计算机安禾16 小时前
【算法分析与设计】第36篇:计算几何基础:凸包问题的分治与扫描线解法
大数据·人工智能·算法·机器学习·剪枝
货拉拉技术16 小时前
飞速发展的计算机视觉
人工智能·算法
如竟没有火炬17 小时前
寻找峰值——二分
java·开发语言·数据结构·python·算法·散列表
noipp17 小时前
推荐题目:洛谷 P1115 最大子段和
算法