【算法复习·总结】背包问题以及它的小伙伴们

背包问题

01背包问题

容量为C的包,一共有N个物品(重量W[i],价值V[i]),如何拿物品使得价值最大?

# 子问题:将前i件物品放入容量为j的背包中
# 转移方程:
#     如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为j的背包中”
#     如果放第i件物品,那么问题就转化为“前i-1件物品放入容量为j-w[i]的背包中”
# 时间复杂度为O(N*C)
# 空间复杂度为O(N*C)
def max_value(N, W, V, C):
    if N == 0 or C == 0:
        return 0 
    dp = np.zeros((N, C+1)) # 将前i件物品装进限重为j的背包可以获得的最大价值
    for j in range(C+1):
        if W[0] <= j:
            dp[0][j] = V[0]
    
    for i in range(1, N):
        for j in range(C+1):
            if W[i] > j:
                dp[i][j] = dp[i-1][j]
            else:
                dp[i][j] = max(dp[i-1][j-W[i]] + V[i], dp[i-1][j])
    return dp[-1][-1]
 
 # 优化空间复杂度
 # 由于转移方程中只用到了dp[i-1],那么可以进行空间复杂度的优化
def max_value(N, W, V, C):
    if N == 0 or C == 0:
        return 0
    dp = np.zeros((C+1))

    for i in range(N):
        for j in reversed(range(W[i], C+1)):
            # 如果是顺序的话,dp[j-w[i]]的值就会被覆盖了,不是i-1时候的值了
            dp[j] = max(dp[j], dp[j-W[i]] + V[i])
    return dp[-1]

完全背包问题

背包容量为C,有N种物品无限个(重量W[i],价值V[i]),如何拿物品使得价值最大?

def max_value(N, W, V, C):
    if N == 0 or C == 0:
        return 0
    dp = np.zeros((C+1))

    for i in range(N):
        for j in reversed(range(W[i], C+1)):
            # 应该为正序:装入第i种物品之后仍然能装第i种物品
            dp[j] = max(dp[j], dp[j-W[i]] + V[i])
    return dp[-1]

# 还有两种办法:转换为01背包问题
# 第一种 max(dp[i-1][j-k*W[i]] + k*V[i], dp[i-1][j])
# 第二种 max(dp[i-1][j-2^k*W[i]] + 2^k*V[i], dp[i-1][j])

多重背包问题

容量为CN种物品(n[i]件, w[i]重量,v[i]价值),如何拿物品使得价值最大?

# 转换为01背包问题
# 第一种 max(dp[i-1][j-k*W[i]] + k*V[i], dp[i-1][j])
# 第二种 max(dp[i-1][j-2^k*W[i]] + 2^k*V[i], dp[i-1][j])
def max_value(N, n, w, v, C):
    if N == 0 or C == 0:
        return 0
    # 拆分物体
    w_new = []
    v_new = []
    for i in range(N):
        k = 1
        while k < n[i]:
            w_new.append(k * w[i])
            v_new.append(k * v[i])
            k = k << 1
        k = k >> 1
        w_new.append(k * w[i])
        v_new.append(k * v[i])
    
    N_new = len(w_new)
    dp = np.zeros((C+1,))
    for i in range(N_new):
        for j in reversed(range(w_new[i], C+1)):
            dp[j] = max(dp[j], dp[j-w_new[i]] + v_new[i])
            
    return dp[-1]

混合背包问题

有的物品可以取1次,有的物品可以取无限次,有的物品可以取有限次(转换为01背包,多重背包和完全背包来解决)

二维背包问题

背包承重A,容量BN种物品(重量a[i],体积b[i],价值v[i]),如何拿物品使得价值最大?

# 状态也加一维即可
# dp[j][k]: 前i个物品放入承重j容量k的背包里的最大价值
def max_value(N:int,A:int, B:int, a:list[int], b:list[int], v:list[int]):
    if N == 0 or A == 0 or B == 0:
        return 0
    dp = np.zeros((A+1, B+1))
    for i in range(N):
        for j in range(1, A+1):
            for k in reversed(range(1, B+1)):
                if j >= a[i] and k >= b[i]:
                    dp[j, k] = max(dp[j-a[i], k-b[i]] + v[i], dp[j][k]) 
    return dp[-1][-1]

另外一种描述:背包承重A,最多只能拾取M件物品,N种物品(重量a[i],价值v[i]),那么就相当于每件物品的“件代价”为`。

分组背包问题

这些物品被划分为N组,每组中的物品(w[i][j], v[i][j])互相冲突,最多选一件。

# 转化为01背包问题
# 一组里面选增益最大的那个
def max_value(N:int,w, v, C):
    if N == 0 or C == 0:
        return 0
        
    dp = np.zeros((C+1, ))
    for i in range(N):
        for j in reversed(range(C+1)):
            for k in range(len(w[0])):  # 对于每组物品的个数
                if w[i][k] > j:
                   continue
                dp[j] = max(dp[j], dp[j-w[i][k]] + v[i][k])
     return dp[-1]

其他变体

背包恰好装满

背包恰好装满时,初始化不同,只有dp[0]初始化为0,其余初始化为-inf即可。(因为最初什么都不装的时候,只有背包容量为0才满足“恰好装满”的条件)

背包的方案数

# 转移方程dp的含义就更改为:第0....i件物品装入背包刚好装满j容量的方案数
def max_value(N, W, V, C):
    if N == 0 or C == 0:
        return 0
    dp = np.zeros((C+1))

    for i in range(N):
        for j in reversed(range(W[i], C+1)):
            # 如果是顺序的话,dp[j-w[i]]的值就会被覆盖了,不是i-1时候的值了
            dp[j] = sum(dp[j], dp[j-W[i]])
    return dp[-1]

对于恰好装满的背包方案数,dp的初始化就不应该是-inf了,应该除了dp[0]都初始化为0

背包的最优方案

对于01背包问题,需要记录每一个物品到底选了还是没选,所以需要多开辟O(N)的空间保存这一选择。

leetcode实践

416. 分割等和子集 - 力扣(LeetCode):恰好装满的01背包问题
322. 零钱兑换 - 力扣(LeetCode): 恰好装满的完全背包问题
494. 目标和 - 力扣(LeetCode):恰好装满的01背包问题
474. 一和零 - 力扣(LeetCode):二维01背包问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值