背包问题

1.参考资料

背包问题从简单到复杂有:

0-1背包:每种物品只能放入一次

完全背包:每种物品放入次数不限

多维费用背包:物品的费用可能有重量和体积等多个方面

复杂约束背包:背包的物品相互约束或依赖

推荐的材料是背包问题九讲,初步学习了0-1背包完全背包问题。

 

2.  0-1背包问题

所有的背包问题都可以由0-1背包演化而来。

2.1 问题定义:

有一些物品,这些物品的重量为[cost i],对应的价值为[value i],将它们装进容量为V的背包,求能获得的最大价值。

2.2 状态定义:

F[i,s]表示将前i件物品装进容量为s的背包时能获得的最大价值,则F[I,V]就是最优解。

2.3 状态转移方程:

F[i,s] = max{ F[i-1,s](不把第i件物品放入背包),F[i-1,s-cost i]+value i(把第i 件物品放入背包)}

2.4 求解算法:

2.5 优化:

上述F数组是I*V,可以优化成一维数组。只要把重量的遍历顺序固定为V---cost i。因为每次更新时要用到前面的值,若从小到大更新,那么后边更新用到的前面的值是更新后的F[i,s-cost i]而非F[i-1, s-cost i]。实际上,求解时通常优先考虑一维数组

算法改为:

 2.6 初始化:

若把题目的要求改为“装满背包时,最大价值多少”,则初始化F[0]=0,F[1..V]为-\infty,因为s=0,问题可能有解,而没东西可放但是容量不为0,说明没有可行解,用无穷来表示这种状态。

其他的背包问题也要注意初始化问题,根据情况选为负无穷或正无穷。

 

3.  完全背包问题

3.1 定义

与0-1背包唯一的不同是每种物品数量不限。

3.2 状态转移方程

F[i,s] = max{ F[i-1,s](根本不把第i件物品放入背包),F[i,s-cost i]+value i(把第i 件物品放入背包一次或多次)}唯一的区别在于第二项从i-1到i。

3.3 求解算法

 与0-1背包唯一的不同是重量的遍历顺序相反,完全背包的每一项更新要用到前边的更新后的值,所以要从小到大遍历,这样计算F[i,s]时,保证s-cost i的位置上是更新后的F[i, s-cost i]而非F[i-1, s-cost i]。

 

解背包问题的注意点

1.初始化

2.遍历顺序

3.数组的下标不要越界,比如 cost i可能大于V

4.  leetcode

416 0-1背包 装满

题目定义

非负数组分为两个子数组,两个子数组的和相等。给定一个数组,请问是否能分开。

代码

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        if not nums:
            return True
        if  len(nums)==1 :
            return True if nums[0]==0 else False
        nums_sum = 0
        for i in nums:
            nums_sum += i
        if nums_sum%2==1:
            return False
        v = nums_sum/2
        dp = [-1]*(v+1)#因为要装满,所以初始化为-1表示无解
        dp[0] = 1#初始化为1表示有解
        for i in nums:
            for idx in range(i,v+1)[::-1]:
                if dp[idx]==1 or dp[idx-i]==1:
                    dp[idx]=1
        return True if dp[-1]==1 else False

因为是要装满且是看问题有无解,所以初始化为-1表示无解,初始化为1表示有解。


322 完全背包 装满

问题定义

给一组人民币取值数组,给定一个目标值,求能用人民币数组表示的方案的最小张数,无解返回-1.

代码

class Solution(object):
    def coinChange(self, coins, amount):
        if not coins or amount<0:
            return -1
        dp = [10000]*(amount+1)
        dp[0] = 0
        for i in coins:
            for idx in range(i,amount+1):
                dp[idx]=min(dp[idx],dp[idx-i]+1)
        return dp[-1] if dp[-1]<10000 else -1

因为要求最小解,所以无解的初始值为正无穷。这是一个通用技巧。


474 0-1背包 二维约束

问题定义

给定0和1的个数,从一个字符串数组中选出不超过0,1的个数的最大字符串数。

 代码

class Solution(object):
    def findMaxForm(self, strs, m, n):
        if not strs:
            return 0
        dp = [[0 for i in range(n+1)] for j in range(m+1)]
        for s in strs:
            c0 = s.count('0')
            c1 = s.count('1')
            if c0>m or c1>n:
                continue
            for x in range(c0,m+1)[::-1]:
                for y in range(c1,n+1)[::-1]:
                    dp[x][y] = max(dp[x][y],dp[x-c0][y-c1]+1)
        return dp[m][n]

 与0-1背包的不同是F成了二维数组,分别表示两个约束的取值。也多了一层循环。

python新建二维list使用列表产生式,此处容易踩坑!!!而且行数列数顺序先后要注意!!!


377  交换内外层循环

问题定义

给出一个数组和目标值,求 和为目标值(正数)的组合数。注意,顺序不一样算两种解。

代码

from collections import deque
class Solution(object):
    def combinationSum4(self, nums, target):
        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>=0:
                    dp[i]+=dp[i-num]
        return dp[-1]

494 巧妙转化为0-1背包

问题定义

给定数组,每个元素可以取为正或负,问有几种方法使数组所有元素和为目标值。

代码

class Solution(object):
    def findTargetSumWays(self, nums, S):
        if not nums:
            return 0
        sum_nums = sum(nums)
        if S>sum_nums or (sum_nums-S)%2==1:
            return 0
        target = (sum_nums-S)/2
        dp = [0]*(target+1)
        dp[0] = 1
        for num in nums:
            for idx in range(num,target+1)[::-1]:
                dp[idx] = dp[idx]+dp[idx-num]
        return dp[-1]

数组全取正数,和为sum_nums,与target差值的一半,就是取值为负的数的和。

问题变成了,选取数组的若干个数,使和为目标值(sum_nums-target)/2。

即0-1背包问题 装满。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值