DP-背包问题系列python

本文介绍了动态规划在解决背包问题中的应用,包括普通版本和空间优化版本的0-1背包问题,以及完全背包问题的解决方案。通过实例展示了如何使用动态规划求解物品装入背包以达到最大价值,并探讨了空间优化技巧,如滚动数组。同时,提供了LeetCode上的相关问题作为实例,如分割等和子集和零钱兑换问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

普通版本:时间:O(N*W);空间:O(N*W)

'''
有n件物品和容量为m的背包 给出i件物品的重量以及价值
求解让装入背包的物品重量不超过背包容量 且价值最大 。
'''
def knapsack(N, W, wts, val):
    # dp[i][j]表示当背包容量为j的情况下,在前i个物品中挑选部分放入背包能得到的最大价值
    dp = [[0] * (W+1) for _ in range(N+1)]
    # n遍历考虑前n个物品是否加入背包中
    for n in range(1, N+1):
        # w遍历背包的容量
        for w in range(1, W+1):
            # 背包剩余空间不足
            if w - wts[n-1] < 0:
                dp[n][w] = dp[n - 1][w]
            # I. 当第n个物品不选时,dp值与第n-1次的dp值相同
            # II.当第n个物品选择时,dp值为第n-1次中背包剩余容量为【w - wts[n-1]】的dp值
            #    加上第n个物品的价值
            else:
                dp[n][w] = max(dp[n - 1][w - wts[n-1]] + val[n-1], dp[n - 1][w])
    return dp[N][W]

空间优化版本:时间:O(N*W);空间:O(W)

使用滚动数组的思想,去掉第一维

def knapsack_space_optimize(N, W, wts, val):
    dp = [0] * (W + 1)
    for n in range(N):
        # 限制下界,防数组越界
        # 逆序是防止覆盖n-1次的结果
        for w in range(W, wts[n] - 1, -1):
            dp[w] = max(dp[w], dp[w-wts[n]] + val[n])
    return dp[W]

 对于w逆序操作的理解:在考虑第n个物品时, w的逆序遍历过程中,dp[w]表示的是n-1次时dp[n-1][w]的结果,dp[w-wts[n]]表示的是n-1次时dp[n-1][w-wts[n]]的结果.

# 测试用例
input:
N = 3
W = 4
wts = [2, 1, 3]
val = [4, 2, 3]

output:
6

0-1背包类似问题:LeetCode 416.分割等和子集

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        sums = sum(nums)
        if sums % 2 == 1:
            return False
        half = sums // 2
        dp = [False] * (half + 1)
        # 目标为0时,只要一个数字都不取就总是能达到的
        dp[0] = True
        for i in range(len(nums)):
            for j in range(half, 0, -1):
                # 防数组越界
                if j - nums[i] >= 0:
                    # 只要有一种情况是可以组成和为half那么就赋值为True,使用or
                    dp[j] = dp[j] or dp[j - nums[i]]
        return dp[half]

完全背包问题:LeetCode 518.零钱兑换II

class Solution(object):
    '''
    时间:O(N*amount)
    空间:O(N*amount)
    '''
    def change(self, amount, coins):
        dp = [[0] * (amount+1) for _ in range(len(coins)+1)]
        # 当amount为0时,无为而治,解决方法为1
        for i in range(len(coins)+1):
            dp[i][0] = 1
        for i in range(1, len(coins)+1):
            for j in range(1, amount+1):
                # 这里的 i-1 非常容易忽略,这里i表示使用第i中硬币,但是对应在数组里的索引是i-1
                if j-coins[i-1] >= 0:
                    # 目标是求硬币所有组合可能情况,所以要加起来
                    dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]
                else:
                    dp[i][j] = dp[i-1][j]
        return dp[len(coins)][amount]

    '''
    空间优化:滚动数组
    时间:O(N*amount)
    空间:O(amount)
    '''
    def change_optim(self, amount, coins):
        dp = [0] * (amount+1)
        dp[0] = 1
        for i in range(len(coins)):
            # 这里不需要逆序
            for j in range(1, amount+1):
                if j-coins[i] >= 0:
                    dp[j] = dp[j] + dp[j-coins[i]]
        return dp[amount]

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值