字节跳动面试题.背包系列「01背包、完全背包、二维费用背包、多重背包」

目录

01背包

完全背包(每件物品可以选无限次)

二维费用的背包

多重背包

扩展多重背包.扩大数据范围

 


01背包

1.题目描述

2.解题思路

f[i][j]:表示只看前i个物品,总体积是j的情况下,最大价值是多少

返回max{f[N][0~V]}

当前物品可以装的下(if v[i]<=j)时:

1.选第i个物品:f[i][j]=f[i-1][j-v[i]]+w[i]

2.不选第i个物品:f[i][j]=f[i-1][j]

f[i][j]=max{1. , 2. } 两种情况取最大值

当前物品装不下(if v[i]>j)时:

f[i][j]=f[i-1][j]

注意初始化方式:

如果f[j]表示体积小于等于j的最大价值,那么f[]应该全部初始化成0;
如果表示体积恰好是j的最大价值,那么f[]应该初始化成负无穷(除了f[0] = 0)

  • 那么为什么当表示体积小于等于j时的最大价值时,要初始化成0呢? 这里最好从原始状态来考虑:dp的边界是f[0][j],表示一个物品都不选,体积不超过j的最大价值,那么“一件物品都不选”这个方案是合法的,它的价值是0,因此需要把f[0][j]初始化成0。
  • 当表示体积恰好等于j时的最大价值时,还是从定义出发,f[0][j],表示一件物品都不选,体积恰好是j的最大价值,那么只有当j == 0时有合法方案,j等于其他值时没有合法方案,因此只有f[0][0] = 0,其余f[0][j] = -INF。

3.代码实现

N,V = list(map(int,input().split()))
v=[]
w=[]
v.append(0)
w.append(0)
for _ in range(N):
    v_, w_ =list(map(int,input().split()))
    v.append(v_)
    w.append(w_)
f=[[0 for _ in range(V+1)] for _ in range(N+1)]
for i in range(1,N+1):
    for j in range(0,V+1):
        f[i][j]=f[i-1][j]
        if v[i]<=j:
            f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i][j])
res=0
for j in range(0,V+1):
    res=max(res,f[N][j])
print (res)
    

4.代码优化

其实数组每次写入一行数据都是根据上一行的数据进行写入,不需要用二维数组,可以用一维数组,每次根据自身进行更新即可,重点是[i-1][j - v[i]]这个数据的意思是我要在表中上一行找比j小的数据,那换成一维的话,我找的是自身(只有一行,没有上一行)j左边的数据,这个数据必须保证是上一次(i-1)写入的,肯定不能从左向右写入数据,那样的话[i-1][j - v[i]] 对应的就是这一次(i)写入的,上一次数据没有利用就丢失了,所以j循环应该从V到1,从右向左向一维数组写d入数据。

N,V = list(map(int,input().split()))
v=[]
w=[]
v.append(0)
w.append(0)
for _ in range(N):
    v_, w_ =list(map(int,input().split()))
    v.append(v_)
    w.append(w_)
# 如果f[j]表示体积小于等于j的最大价值,那么f[]应该全部初始化成0;
# 如果表示体积恰好是j的最大价值,那么f[]应该初始化成负无穷(除了f[0] = 0)

f=[0 for _ in range(V+1)]
for i in range(1,N+1):
    for j in range(V,-1,-1):
        if v[i]<=j:
            # 如果j像优化之前的代码正向算,那实际代表的是i那层的j,而我们需要的是i-1那层的是j
            f[j]=max(f[j-v[i]]+w[i],f[j])
print (f[V])
    

参考:https://www.acwing.com/problem/content/video/2/

参考:https://blog.csdn.net/m0_37907835/article/details/78991461

完全背包

1.题目描述 

2.解题思路

和01背包的区别:每件物品可以选无限次

直接上一维:

j从大到小枚举:f[j-v[i]]是i-1状态的 f[i-1][[j-v[i]]

j从小到大枚举:f[j-v[i]]是i状态的f[i][[j-v[i]],此时这个j-v[i]在之前被算过了,已经包含第i个物品了,因为当前这个i是可以取无限个的,所以应该包含这个i

3.代码实现

N,V = list(map(int,input().split()))
v=[]
w=[]
v.append(0)
w.append(0)
for _ in range(N):
    v_, w_ =list(map(int,input().split()))
    v.append(v_)
    w.append(w_)
# 如果f[j]表示体积小于等于j的最大价值,那么f[]应该全部初始化成0;
# 如果表示体积恰好是j的最大价值,那么f[]应该初始化成负无穷(除了f[0] = 0)

f=[0 for _ in range(V+1)]
for i in range(1,N+1):
    for j in range(0,V+1):
        if v[i]<=j:
            # 如果j像优化之前的代码正向算,那实际代表的是i那层的j,而我们需要的是i-1那层的是j
            f[j]=max(f[j-v[i]]+w[i],f[j])
print (f[V])

二维费用的背包

1.题目描述

2.解题思路

费用限制为重量和体积,每个物品个数只有1个

3.代码实现

N,V,M = list(map(int,input().split()))
v=[]
m=[]
w=[]

v.append(0)
m.append(0)
w.append(0)
for _ in range(N):
    v_, m_, w_ =list(map(int,input().split()))
    v.append(v_)
    m.append(m_)
    w.append(w_)

f=[[0 for _ in range(M+1)] for _ in range(V+1)]
for i in range(1,N+1):
    for j in range(V,-1,-1):
        if v[i]<=j:
            for k in range(M,-1,-1):
                if m[i]<=k:
                    f[j][k]=max(f[j-v[i]][k-m[i]]+w[i],f[j][k])
print (f[V][M])
    

 

多重背包

 

1.题目描述

2.解题思路

多加一层循环,用于限制每个物品的使用个数

初始化时如果把f当中的所有元素初始化为0,求f[V]

初始化时如果f[0]=0,其余元素为--INF,求max{f[0~V]}

3.代码实现

N,V = list(map(int,input().split()))
v=[]
w=[]
s=[]

v.append(0)
w.append(0)
s.append(0)
for _ in range(N):
    v_, w_, s_ =list(map(int,input().split()))
    v.append(v_)
    w.append(w_)
    s.append(s_)

f=[0 for _ in range(V+1)]
for i in range(1,N+1):
    for j in range(V,-1,-1):
        k=1
        while k<=s[i] and k*v[i]<=j:
            f[j]=max(f[j-k*v[i]]+k*w[i],f[j])
            k+=1
print (f[V])
    

扩展多重背包.扩大数据范围

 

1.题目描述

2.解题思路

可以将s拆成s份,每个物品选或者不选,只能选一次,转换成0,1背包问题,但是这样复杂度还是不满足条件

考虑一个数s,至少选择(logs)向上取整个数才可以表示出0~7所有的数(将每个数转化为二进制表示,则每个位可以选或不选(1或0))

                                                                                   

考虑10这个数,如果选择1,2,4,8四个数,会表示出0~15的每种情况,多出不需要的11~15,如果1,2,4,(10-7)这四个数就可以恰好表示0~10了:

                                                                                  

s=s-1-2-4-...... 直到小于0为止,剩下的那个数就是需要后补的

因此,我们可以把问题转成01背包问题了,只需判断这logs个数选或者不选即可

3.代码实现

N,V = list(map(int,input().split()))
v=[]
w=[]
s=[]

v.append(0)
w.append(0)
s.append(0)
for _ in range(N):
    v_, w_, s_ =list(map(int,input().split()))
    v.append(v_)
    w.append(w_)
    s.append(s_)
# 存入的是一个合并后的物品的元组 (v_, w_)
goods=[]
f=[0 for _ in range(V+1)]
# 合并物品
for i in range(1,N+1):
    k=1
    while k<=s[i]:
        s[i]-=k
        goods.append((k*v[i],k*w[i]))
        k=k*2
    # s-1-2-4-......剩下的
    if s[i]>0:
        goods.append((s[i]*v[i],s[i]*w[i]))
# 01背包过程
for good in goods:
    for j in range(V,-1,-1):
        if good[0]<=j:
            f[j]=max(f[j-good[0]]+good[1], f[j])
print (f[V])

参考链接:https://www.acwing.com/problem/content/5/

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值