手撕算法——01背包问题

题目描述

01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤100

0
输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

解决思路
01背包问题可以用动态规划来求解
按照之前文章提到的动态规划的解题模板:
(1)确定数组元素含义
int dp[][]=new int[N+1][V+1]
dp[i][j] 表示背包容积为j数量为i时最大的价值。
(2) 确定状态转移方程
当计算第i个物品时无非两种情况,第一种是放该物品,第二种是不放该物品。
第一种情况:dp[i][i]=dp[i-1][j]
第二种情况 dp[i][j]=dp[i-1][j-v[i]]+w[i],这种情况的前提条件是j>=v[i]
因此 dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-v[j]]+w[j])
(3)确定初始值
dp[0][0…j]=0, dp[0…i][0]即不放任何物品,体积为0时,总价值为0;
因为java中new出来的数组初始值为0,因此这里不需要任何的操作
(4)根据初始值和状态转移方程确定数组元素的值
(5)返回结果 dp[N][V]
(6)考虑状态压缩

使用二维数组的代码如下:

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
     
        Scanner scanner =new Scanner(System.in);
        int N=scanner.nextInt();//N行  物品数量
        int V=scanner.nextInt();//背包容积
        int v[]=new int[N+1];
        int w[]=new int[N+1];
      
        for(int i=1;i<=N;i++){
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
        }
        
        int dp[][]=new int[N+1][V+1];//状态数组
        dp[0][0]=0;
        for(int i=1;i<=N;i++){
            for(int j=1;j<=V;j++){
                dp[i][j]=dp[i-1][j];
                if(j-v[i]>=0){
                    dp[i][j]=Math.max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
                }
                
            }
        }
     
        System.out.println(dp[N][V]);
    }
}

压缩为一维数组与二维数组不同之处在于遍历的时候需要逆序,是为了第i轮中不覆盖到第i-1轮中的结果。可以参考下文:
对使用倒序的一维数组解决0/1背包问题的理解
压缩为意味数组的代码如下:

import java.util.*;
public class Main{
    public static void main(String[] args){
     
        Scanner scanner =new Scanner(System.in);
        int N=scanner.nextInt();//N行  物品数量
        int V=scanner.nextInt();//背包容积
        int v[]=new int[N+1];
        int w[]=new int[N+1];
      
        for(int i=1;i<=N;i++){
            v[i]=scanner.nextInt();
            w[i]=scanner.nextInt();
        }
        
        int dp[]=new int[V+1];//用来表示该体积下可以容纳物品的最大总价值

        for(int i=1;i<=N;i++){
            for(int j=V;j>=v[i];j--){
               
                dp[j]=Math.max(dp[j],dp[j-v[i]]+w[i]);
                
                
            }
        }
     
        System.out.println(dp[V]);
    }
}

更进一步,记录一下中兴的在线笔试题计算反应堆的最大能量,该题目是01背包问题的变形,不再做过多的赘述

public class Test2_01 {

	public static void main(String[] args) {
		int reactorCap = 100; // 反应堆的容量(V)
		int numberOfRadLiquid = 5; // 现有小瓶数量(N)
		int criticalMass = 15; // 反应堆的最大临界质量(M)
		int volume[] = { 50, 40, 30, 20, 10 };// 体积
		int masses[] = { 1, 2, 3, 9, 5 };// 质量
		int energies[] = { 300, 480, 270, 200, 180 }; // 能量

		int dp[][][]=new int[numberOfRadLiquid+1][criticalMass+1][reactorCap+1];
		
		for(int i=1;i<=numberOfRadLiquid;i++) {
			for(int j=1;j<=criticalMass;j++) {
				for(int k=1;k<=reactorCap;k++) {
					dp[i][j][k]=dp[i-1][j][k];
					if(j>=masses[i-1]&&k>=volume[i-1]) {
						dp[i][j][k]=Math.max(dp[i][j][k], dp[i-1][j-masses[i-1]][k-volume[i-1]]+energies[i-1]);
					}
				}
			}
		}
		
		System.out.print(dp[numberOfRadLiquid][criticalMass][reactorCap]);
	}
}

空间压缩

public class Test2_02 {
	public static void main(String[] args) {
		int reactorCap = 100; // 反应堆的容量(V)
		int numberOfRadLiquid = 5; // 现有小瓶数量(N)
		int criticalMass = 15; // 反应堆的最大临界质量(M)
		int volume[] = { 50, 40, 30, 20, 10 };// 体积
		int masses[] = { 1, 2, 3, 9, 5 };// 质量
		int energies[] = { 300, 480, 270, 200, 180 }; // 能量

		int dp[][]=new int[criticalMass+1][reactorCap+1];
		
		for(int i=1;i<=numberOfRadLiquid;i++) {
			for(int j=criticalMass;j>=masses[i-1];j--) {
				for(int k=reactorCap;k>=volume[i-1];k--) {
					dp[j][k]=Math.max(dp[j][k], dp[j-masses[i-1]][k-volume[i-1]]+energies[i-1]);	
				}
			}
		}
		System.out.print(dp[criticalMass][reactorCap]);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值