动态规划经典问题:0-1背包问题详解

动态规划经典问题:0-1背包问题详解

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

1. 背包问题概述

背包问题是动态规划领域中一类非常经典的问题,它描述的是在有限的资源约束下如何做出最优选择的问题。这类问题在实际应用中非常广泛,比如资源分配、投资组合、装载问题等都可以抽象为背包问题。

1.1 背包问题的基本定义

给定一组物品,每种物品都有自己的重量和价值。在限定的总重量内,我们如何选择物品才能使总价值最大。根据物品的不同限制条件,背包问题可以分为以下几种类型:

  • 0-1背包问题:每种物品只有一件,可以选择放或不放
  • 完全背包问题:每种物品有无限件
  • 多重背包问题:每种物品有有限件
  • 分组背包问题:物品被分为若干组,每组只能选一件
  • 混合背包问题:包含以上多种情况的组合

2. 0-1背包问题详解

2.1 问题描述

0-1背包问题是背包问题中最基础的形式,其特点是每种物品只有一件,要么选择放入背包,要么不放入。

形式化定义

  • 有n件物品和一个容量为W的背包
  • 第i件物品的重量为weight[i],价值为value[i]
  • 每种物品只有一件,可以选择放入或不放入
  • 目标是在不超过背包容量的前提下,使背包中物品的总价值最大

2.2 基本解法:二维动态规划

2.2.1 状态定义

我们定义一个二维数组dp,其中dp[i][w]表示考虑前i件物品,在背包容量为w时可以获得的最大价值。

2.2.2 状态转移方程

对于每件物品,我们有两种选择:

  1. 不放入背包:此时最大价值等于前i-1件物品在容量w时的最大价值
  2. 放入背包:此时最大价值等于前i-1件物品在容量w-weight[i]时的最大价值加上当前物品的价值

因此状态转移方程为:

dp[i][w] = max(dp[i-1][w], dp[i-1][w-weight[i]] + value[i])
2.2.3 边界条件
  • 当背包容量为0时,dp[i][0] = 0
  • 当没有物品可选时,dp[0][w] = 0
2.2.4 代码实现
def zeroOnePack(weight, value, W):
    n = len(weight)
    dp = [[0]*(W+1) for _ in range(n+1)]
    
    for i in range(1, n+1):
        for w in range(W+1):
            if w < weight[i-1]:
                dp[i][w] = dp[i-1][w]
            else:
                dp[i][w] = max(dp[i-1][w], dp[i-1][w-weight[i-1]] + value[i-1])
    
    return dp[n][W]
2.2.5 复杂度分析
  • 时间复杂度:O(nW),需要填充n×W的二维表格
  • 空间复杂度:O(nW),需要存储整个二维表格

2.3 空间优化:滚动数组

观察状态转移方程可以发现,dp[i][w]只依赖于dp[i-1][...],因此可以使用一维数组来优化空间。

2.3.1 优化思路

将二维数组压缩为一维数组dp[w],表示容量为w时的最大价值。为了保证在计算dp[w]时,dp[w-weight[i]]仍然是上一轮的值,需要逆序遍历背包容量。

2.3.2 优化后的代码
def zeroOnePackOpt(weight, value, W):
    n = len(weight)
    dp = [0]*(W+1)
    
    for i in range(n):
        for w in range(W, weight[i]-1, -1):
            dp[w] = max(dp[w], dp[w-weight[i]] + value[i])
    
    return dp[W]
2.3.3 复杂度分析
  • 时间复杂度:O(nW),与二维解法相同
  • 空间复杂度:O(W),只需存储一维数组

3. 0-1背包问题的应用

3.1 分割等和子集问题

问题描述:给定一个只包含正整数的数组,判断是否可以将数组分成两个子集,使得两个子集的元素和相等。

解题思路

  1. 计算数组总和sum,如果sum为奇数,直接返回False
  2. 目标为找到一些元素,使其和等于sum/2
  3. 将问题转化为0-1背包问题:
    • 背包容量为sum/2
    • 物品重量和价值均为数组元素
    • 判断是否能恰好装满背包

代码实现

def canPartition(nums):
    total = sum(nums)
    if total % 2 != 0:
        return False
    
    target = total // 2
    dp = [False]*(target+1)
    dp[0] = True
    
    for num in nums:
        for i in range(target, num-1, -1):
            dp[i] = dp[i] or dp[i-num]
    
    return dp[target]

4. 总结

0-1背包问题是动态规划中的经典问题,掌握其解法对于理解动态规划思想非常重要。关键点包括:

  1. 正确定义状态和状态转移方程
  2. 理解二维解法的基本原理
  3. 掌握空间优化的滚动数组技巧
  4. 学会将实际问题转化为背包问题

通过不断练习和思考,可以更好地掌握这类问题的解法,并能够灵活应用到各种实际问题中。

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

褚添北Dwight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值