动态规划-背包问题

1、基础问题:

给你一个可以装载重量为w的背包和n个物品,每个物品有价值和重量2个属性,其中第i个物品重量wt[i],价值为val[i],现在用这个背包装物品,限定重量下最多价值是多少

动态规划标准套路:

1、确定状态和选择

状态:背包重量和可选择的物品

选择:装进背包和不装进背包

2、dp数组定义:

dp[i][j] 对于前i个物品,当背包重量为j时的最大价值时dp[i][j]

初始状态:

dp[0][……]:  没有物品时为0

dp[……][0]: 背包没有空间时能装的价值是0

返回的最终解:

dp[N][W]

3、状态转移矩阵

dp[i][j]=

1、if 没有把物品放进背包:dp[i-1][j]  维持之前结果

2、if 把物品放进背包:max(dp[i-1][j], dp[i-1][j-wt[i-1]]+val[i-1])

val和wt的索引是从1开始的,所以这里wt val用i-1代表第i个物品的状态

模板

dp[N+1][W+1]
dp[0][……] = 0
dp[……][0] = 0
for i in [1...N]:
    for j in [1...W]:
        把物品i装进背包
        不把物品i装进背包

return dp[N][W]

 实际解法:

def knapsack(w,n,wt,val):
    dp = [[0]*(w+1) for _ in range(n+1)]

    for i in range(1,n+1):
        for j in range(1,w+1):
            if j - wt[i-1] >= 0:
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-wt[i-1]] + val[i-1])
            else:
                dp[i][j] = dp[i-1][j]
    return dp[n][w]

 也可以将wt,val补第一个数据为0,拉齐下标:

def knapsack(w,n,wt,val):
    dp = [[0]*(w+1) for _ in range(n+1)]
    wt.insert(0,0)
    val.insert(0,0)
    print(dp)
    print(wt,val)

    for i in range(1,n+1):
        for j in range(1,w+1):
            if j - wt[i] < 0:
                dp[i][j] = dp[i - 1][j]
            else:
                dp[i][j] = max(dp[i-1][j-wt[i]] + val[i],dp[i-1][j])
    return dp[n][w]

 状态压缩(这里是遍历,不是全排列,为了避免重复内循环使用逆序)

(状态压缩的分类详解,可以参考动态规划状态压缩详解https://blog.csdn.net/weixin_43298886/article/details/110152066 )

def knapsack(w,n,wt,val):
    dp = [0 for _ in range(w+1)]

    for i in range(0,n):
        for j in range(w,-1,-1):
            if j - wt[i] >= 0:
                dp[j] = max(dp[j],dp[j-wt[i]]+val[i])
    return dp[w]

2、进阶:子集背包问题

416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

可以分解成sum(nums)//2的问题

基础版

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total %2 == 1:return False
        total = total // 2
        n = len(nums)

        dp = [[False]*(total+1) for _ in range(n+1)]
        for i in range(n+1):
            dp[i][0] = True

        for i in range(1, n+1):
            for j in range(1,total+1):
                if j - nums[i-1] < 0:
                    dp[i][j] = dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j]|dp[i-1][j-nums[i-1]]
        return dp[n][total]

基础版代码类似

在这个基础上进行状态压缩

状态压缩(内循环逆序避免重复遍历)

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total %2 == 1:return False
        total = total // 2
        n = len(nums)

        dp = [False for _ in range(total+1)]
        dp[0] = True
        #待优化
        for i in range(n):
            for j in range(total,-1,-1):
                if j - nums[i] >=0:
                    dp[j] =dp[j] | dp[j-nums[i]]

        return dp[total]

更近一步,j的遍历可以进一步优化

j-nums[i] >=0 的判定可以放在for j in range(total,num-1,-1)进行,这个循环中j 必然大于或者=num

备注:for j in range(total,num-1,-1)取值范围为[num,total]

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total %2 == 1:return False
        total = total // 2
        n = len(nums)

        dp = [False for _ in range(total+1)]
        dp[0] = True

        for i,num in enumerate(nums):
            for j in range(total,num-1,-1):
                dp[j] =dp[j] | dp[j-num]

        return dp[total]

3、完全背包问题

518. 零钱兑换 II

难度中等600收藏分享切换为英文接收动态反馈

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。 

题目数据保证结果符合 32 位带符号整数。

a、基础状态转移矩阵:

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        n = len(coins)
        dp = [[0]*(amount+1) for _ in range(n+1)]

        for i in range(n+1):
            dp[i][0] = 1
        
        for i in range(1,n+1):
            for j in range(1,amount+1):
                if j >= coins[i-1]:#coins 和 dp的起始索引不一样,coins[i-1]代表第i个元素,            
                                   #dp[i-1]代表第i-1个元素状态
                    dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]
                else:
                    dp[i][j] = dp[i-1][j]
        
        return dp[n][amount]

b、状态压缩(全排列,允许重复遍历)

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        n = len(coins)
        dp = [1] + [0 for _ in range(amount)]
        
        for i in range(n):
            for j in range(1,amount+1):
                if j >= coins[i]:
                    dp[j] = dp[j]+dp[j-coins[i]]

        return dp[amount]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值