leetcode-背包问题

在这里插入图片描述

1. 三种背包问题

1.1 0-1背包

有N件物品和一个容量为V的背包,第i件物品消耗的容量为Ci,价值为Wi,求解放入哪些物品可以使得背包中总价值最大。

1.2 完全背包:

有N种物品和一个容量为V的背包,每种物品都有无限件可用,第i件物品消耗的容量为Ci,价值为Wi,求解放入哪些物品可以使得背包中总价值最大。

1.3 多重背包:

有N种物品和一个容量为V的背包,第i种物品最多有Mi件可用,每件物品消耗的容量为Ci,价值为Wi,求解入哪些物品可以使得背包中总价值最大。

三种背包问题都有一个共同的限制,那就是背包容量,背包的容量是有限的,这便限制了物品的选择,而三种背包问题的共同目的,便是让背包中的物品价值最大。

不同的地方在于物品数量的限制,01背包问题中,每种物品只有一个,对于每种物品而言,便只有选和不选两个选择。完全背包问题中,每种物品有无限多个,所以可选的范围要大很多。在多重背包问题中,每种物品都有各自的数量限制。

三种背包问题虽然对于物品数量的限制不一样,但都可以转化为01背包问题来进行思考。在完全背包问题中,虽然每种物品都可以选择无限个,但由于背包容量有限,实际上每种物品可以选择的数量也是有限的,那么将每种物品都看做是 V/Ci 种只有一件的不同物品,不就成了01背包问题吗?对于多重背包也是如此,只是每种物品的膨胀数量变成了 min{Mi, V/Ci}。

所以说,01背包问题是所有背包问题的基础,弄懂了01背包问题后,完全背包和多重背包就没有什么难的地方了。

下面我们来对比一下三种背包问题的状态转移方程,以便更好的理解它们之间的联系:

01背包
F[i,V] = max{F[i-1,V], F[i-1,V-vi] + wi}
完全背包
F[i,V] = max{F[i-1,V-kvi] + kwi | 0 <= kvi <= v}
多重背包
F[i,V] = max{F[i-1,V-k
vi] + k*wi | 0 <= k <= Mi}

2. 0-1背包

N代表物品数量,
vi代表第i个物品占用的容量,
V代表背包总容量,
wi代表第i个物品的价值
f [ i , j ] :从前 i 件物品中选择放入容量为 j 的背包最大的价值。

状态转移方程为:
f[i][V]=max{ f[i-1][V],f[i-1][V-v[i]]+w[i] }。

// 朴素
f[i][j]
   for (int i = 1; i <= N; i++) {
       for (int j = 0; j <= V; j++) {
           f[i][j] = f[i - 1][j];
           if (j >= v[i]) 
                f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
       }
   }

一维优化——对代码做等价变形
分析:f[j] = max(f[j], f[j-Ci)+Wi)要使右边的f表示的是i-1时的值,只需要逆序遍历j,这样右边的f还没被更新,表示的是循环i-1时的值,因此可以等价变形

//倒序
f[j]
for (int i = 1; i <= N; i++) {
        for (int j = V; j >= v[i]; j--) {         // 倒序
            f[j] = max(f[j], f[j - v[i]] + w[i]);
            // 朴素 f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
        }
    }
def ZeroOneKnapsack(F,V, C,W)
    for v = V to C
        F[v] = max{F[v],F[v-C] + W}

for i = 1 to N
    ZeroOneKnapsack(F,V,vi,wi)

3. 完全背包

N代表物品数量,
vi代表第i个物品占用的容量,
V代表背包总容量,
wi代表第i个物品的价值
f [ i , j ] :从前 i 件物品中选择放入容量为 j 的背包最大的价值。

状态转移方程为:
F[i,V] = max{F[i-1,V-kvi] + kwi | 0 <= k*vi <= v}

def CompleteKnapsack(F,V,C,W)
    for v = C to V
        F[v] = max{F[v],F[v-C] + W}
for i = 1 to N
    CompleteKnapsack(F,V, vi,wi)

常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。
以下题目整理来自大神CyC,github地址:
github
我在大神整理的基础上,又做了细分的整理。分为三类。
1、组合问题:
377. 组合总和 Ⅳ
494. 目标和
518. 零钱兑换 II
2、True、False问题:
139. 单词拆分
416. 分割等和子集
3、最大最小问题:
474. 一和零
322. 零钱兑换

组合问题公式. dp[i] += dp[i-num]
True、False问题公式. dp[i] = dp[i] or dp[i-num]
最大最小问题公式dp[i] = min(dp[i], dp[i-num]+1)或者dp[i] = max(dp[i], dp[i-num]+1)

以上三组公式是解决对应问题的核心公式。

当然拿到问题后,需要做到以下几个步骤:
1.分析是否为背包问题。
2.是以上三种背包问题中的哪一种。
3.是0-1背包问题还是完全背包问题。也就是题目给的nums数组中的元素是否可以重复使用。
4.如果是组合问题,是否需要考虑元素之间的顺序。需要考虑顺序有顺序的解法,不需要考虑顺序又有对应的解法。

接下来讲一下背包问题的判定
背包问题具备的特征:给定一个target,target可以是数字也可以是字符串,再给定一个数组nums,nums中装的可能是数字,也可能是字符串,问:能否使用nums中的元素做各种排列组合得到target。

背包问题技巧:

1.如果是0-1背包,即数组中的元素不可重复使用,nums放在外循环,target在内循环,且内循环倒序;
for num in nums:
for i in range(target, nums-1, -1):

2.如果是完全背包,即数组中的元素可重复使用,nums放在外循环,target在内循环。且内循环正序。
for num in nums:
for i in range(nums, target+1):

3.如果组合问题需考虑元素之间的顺序,需将target放在外循环,将nums放在内循环。
for i in range(1, target+1):
for num in nums:
代码
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
if not nums:
return 0
dp = [0] * (target+1)
dp[0] = 1
for i in range(1,target+1):
for num in nums:
if i >= num:
dp[i] += dp[i-num]
return dp[target]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值