四种背包问题总结

1、01背包

问题描述:有n种物品,每种物品有价值v和重量w,给定一个容量为c的背包,每种物品只能选一个,求背包能装物品的最大价值。

解法:用dp(i)( c)来表示当前背包剩余容量为c时,前i个物品的最大价值。

之所以用这两个状态,是因为背包不同容量时候,选择也不一样。

状态转移情况是选择或者不选择第i个物品,状态方程为:

(注意这个选择或者不选择第i个物品,它表示组合数,与顺序无关,尽管我们是按照顺序列举i的,但是我们列举第i个的时候就默认包含了所有的0-i,顺序列举只是为了拆分子问题。这和爬楼梯问题有本质区别,爬楼梯是有顺序的,是排列数,是:dp[n]=dp[n-1]+dp[n-2])

dp(i)( c) = max( dp(i-1)( c), dp(i-1)(c-wi)+vi );

public int f(int[] v,int w[],int n){
    int[][] dp = new int[n+1][C+1];//此时初始化为0,不需要单独写
	for(int i = 1;i<=n;i++){
    	for(int c=1;c<=C;c++){
       		if(w[i]>c){
            	dp[i][c]=dp[i-1][c];
        	}
        	else{
            	d[i][c] = Math.max(dp[i-1][c],dp[i-1][c-w[i]]+v[i]);
        	}
    	}
    return dp[n][c];
	}
}

有代码可见,每一行的状态只和上一行有关系,因此可以用空间压缩:

public int f(int[] v,int w[],int n){
    int[] dp = new int[C+1];
	for(int i = 1;i<=n;i++){
    	for(int c=C;c>=w[i];c--){//倒序是为了防止覆盖
            d[c] = Math.max(dp[c],dp[c-w[i]]+v[i]);
    	}
    return dp[c];
	}
}

若题目要求恰好装满的最大价值,则没恰好装满的情况都是无解的情况,此时只需要改变初始值就可以,这样,不能恰好装满的都会被无穷小填充

见leetcode 分割等和子集(这也是亲身经历惨痛的腾讯面试题)

public int f(){
    int[][] dp = new int[n+1][C+1];
    for(int i =1;i<=C;i++){
        dp[0][i]=Integer.MIN_VALUE;
    }
    dp[0][0]=0;
	for(int i = 2;i<=n;i++){
    	for(int c=1;c<=C;c++){
       		if(w[i]>c){
            	dp[i][c]=dp[i-1][c];
        	}
        	else{
            	d[i][c] = Math.max(dp[i-1][c],dp[i-1][c-w[i]]+v[i]);//此处控制无解情况
                //若不选第i个,则由i-1个控制,若选择,则由dp[i-1][c-w[i]]控制
        	}
    	}
    return dp[n][c];
	}
}

爬楼梯问题:

有n个台阶,每次可以上w=[1,2,3,4]个台阶,问从0爬到n有多少种方法。

解法:每次列举w,状态方程dp[n]=dp[n-w[0]]+dp[n-w[1]]+…+dp[n-w[m]]

此题看似和背包问题比较相似,但是有很大区别,比如第一步走1个台阶,第二步走两个台阶,和第一步走两个第二步走一个不是同一情况。但是对于背包,则没有先后之分,都一样。

2、完全背包

问题描述:有n种物品,每种物品有价值v和重量w,给定一个容量为c的背包,每种物品可以选任意个,求背包能装物品的最大价值。

这个问题和01背包的区别是,我们进行状态转移时候,不仅考虑选或者不选,而是选0件,选1件,一直到选C/w[i]件。

可以写出状态方程:

dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i],dp[i-1][j-2w[i]]+2v[i]...)
//dp[i][j]表示前i个物品组成最大容量为j的背包的最大价值

此时解题的代码可以写为:

public int f(){
    int[][] dp = new int[n+1][C+1];
	for(int i = 1;i<=n;i++){
    	for(int j=1;j<=C;j++){
       		for(int k=0;k*w[i]<j;k++){
                dp[i][j]=Math.max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
            }
    	}
    return dp[n][c];
	}
}

上述的复杂度是C^2 *N,考虑优化状态方程:

dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i],dp[i-1][j-2w[i]]+2v[i]...)(1)
    
把dp[i][j-w[i]]带入上述方程:
dp[i][j-w[i]]=Math.max(dp[i-1][j-w[i]],dp[i-1][j-2w[i]]+v[i]...)(2)
    
dp[i][j-w[i]]+v[i]=v[i]+Math.max(dp[i-1][j-w[i]],dp[i-1][j-2w[i]]+2v[i]...)(3)
=Math.max(dp[i-1][j-w[i]]+v[i],dp[i-1][j-2w[i]]+2v[i]...)(4)
    
公式(1)中的后面的项和公式(4)后面的项一样,可以合并:

dp[i][j]=Math.max(dp[i-1][j],dp[i][j-w[i]]+v[i])

由此我们得到优化的转移方程,复杂度是C*n。

dp[i][j]=Math.max(dp[i-1][j],dp[i][j-w[i]]+v[i])

上述方程也可以有另一种解释:对于第i个物品,我们面临两种互补的选择:要么不选i,要么至少选一个i,两种情况不仅互斥,他们的并集就是全集,所以称之为互补。至于为什么至少选一个i是这种形式:

dp[i][j-w[i]]+v[i]

因为我们要消除选择一个i带来的影响,而不管接下来会怎么样,就是至少选择一个。就像正则表达式匹配一样。正则表达式匹配

3、多重背包

问题描述:有n种物品,每种物品有价值v和重量w,给定一个容量为c的背包,每种物品可以选p个,求背包能装物品的最大价值。

此问题明显是完全背包的变形,可以和完全背包几乎一样,只是多了一个最多p个的判断:

public int f(){
    int[][] dp = new int[n+1][C+1];
	for(int i = 1;i<=n;i++){
    	for(int j=1;j<=C;j++){
       		for(int k=0;k*w[i]<j && k<=p[i];k++){
                dp[i][j]=Math.max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
            }
    	}
    return dp[n][c];
	}
}

4、分组背包

问题描述: 物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

问题变成了每组选或者不选

状态方程为:

f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]∣物品i属于组k)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值