01背包问题解法及优化

01背包问题

给定一个容量为C的背包,重量为weight,价值为value的石头,求背包能容纳的最大价值。

weight = [1, 2, 3]
value = [6, 10 ,12]
C = 5

输出:22
背包中放入一个重量为3的石头和一个重量为2的石头
value = 10 + 12 = 22.

递归

	public int findRes(int[] weight, int[] value, int capcity) {
        if(weight.length != value.length) return 0;
        if(capcity < 0)return 0;
        if(weight.length == 0)return 0;
 
        return helper(weight, value, capcity, weight.length - 1);

    }
    //f(n, c)  考虑[0 - n] 中背包重量为c的最优解
    //状态转移方程为  f(n, c) = Math.max(不考虑最后一个石头, 最后一个石头的价值 + 考虑最后一个石头后剩余背包容量的最大值);
    //状态转移方程为  f(n, c) = Math.max(f(n - 1, c), v[n] + f(n - 1, c - w[n]));
    private int helper(int[] weight, int[] value, int capcity, int index) {
        //如果背包中
        if(index < 0 || capcity <= 0)return 0;

        int res = helper(weight, value, capcity, index - 1);
        if(capcity >= weight[index])
            res = Math.max(res, value[index] + helper(weight, value, capcity - weight[index], index - 1));

        return res;
    }

不难发现此种解法会有重叠计算的问题

记忆化搜索-自下而上

我们开辟一个空间存储之前计算的结果

	public int findRes2(int[] weight, int[] value, int capcity) {
        if(weight.length != value.length) return 0;
        if(capcity < 0)return 0;
        if(weight.length == 0)return 0;
        //行:是否考虑该位置的石头 0:只考虑第一个  1:考虑1  n:考虑n
        //列:背包剩余容量
        int memo[][] = new int[weight.length][capcity + 1];
        for(int i = 0; i < memo.length; i ++)
            Arrays.fill(memo[i], - 1);
        return helper2(weight, value, capcity, weight.length - 1, memo);

    }
    //f(n, c)  考虑[0 - n] 中背包重量为c的最优解
    //状态转移方程为  f(n, c) = Math.max(不考虑最后一个石头, 最后一个石头的价值 + 考虑最后一个石头后剩余背包容量的最大值);
    //状态转移方程为  f(n, c) = Math.max(f(n - 1, c), v[n] + f(n - 1, c - w[n]));
    private int helper2(int[] weight, int[] value, int capcity, int index, int[][] memo) {
        //如果背包中
        if(index < 0 || capcity <= 0)return 0;
        if(memo[index][capcity] != -1)return memo[index][capcity];

        int res = helper2(weight, value, capcity, index - 1, memo);
        if(capcity >= weight[index])
            res = Math.max(res, value[index] + helper2(weight, value, capcity - weight[index], index - 1, memo));
        memo[index][capcity] = res;
        return res;
    }

动态规划-自上而下

	//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
    //空间复杂度为O(C * n) 其中C为背包容量,n为石头个数
    private int findRes3(int[] weight, int[] value, int capcity) {
        if(weight.length != value.length) return 0;
        if(capcity <= 0)return 0;
        if(weight.length == 0)return 0;
        //行:是否考虑该位置的石头 0:只考虑第一个  1:考虑1  n:考虑n
        //列:背包剩余容量
        int memo[][] = new int[2][capcity + 1];

        //base case
        for(int i = 0; i <= capcity; i ++) {
            //只考虑第一个,只要容量满足,就可以放入
            if(weight[0] <= i)
                memo[0][i] = value[0];
        }

        for(int i = 1; i < weight.length; i ++) {
            for(int j = 0; j <= capcity; j ++) {
                int tmp = i ;
                //不考虑当前位置的石头
                memo[i][j] = memo[i - 1][j];
                //考虑且要放得下
                if(j >= weight[i])
                    memo[i][j] = Math.max(memo[i][j], value[i] + memo[i - 1][j - weight[i]]);
            }
        }
        return memo[weight.length - 1][capcity];
    }

优化

可以发现i行数据依赖于i - 1行数据,所以我们只要重用空间就可以只使用2行记录状态。

	//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
    //空间复杂度为O(C * 2) 其中C为背包容量
    private int findRes4(int[] weight, int[] value, int capcity) {
        if(weight.length != value.length) return 0;
        if(capcity <= 0)return 0;
        if(weight.length == 0)return 0;
        //行:是否考虑该位置的石头 0:只考虑第一个  1:考虑1  n:考虑n
        //列:背包剩余容量
        int memo[][] = new int[2][capcity + 1];

        //base case
        for(int i = 0; i <= capcity; i ++) {
            //只考虑第一个,只要容量满足,就可以放入
            if(weight[0] <= i)
                memo[0][i] = value[0];
        }

        for(int i = 1; i < weight.length; i ++) {
            for(int j = 0; j <= capcity; j ++) {
                int tmp = i ;
                //不考虑当前位置的石头 [0]的位置放偶数 [1]的位置放奇数
                memo[i & 1][j] = memo[(i - 1) & 1][j];
                //考虑且要放得下
                if(j >= weight[i])
                    memo[i & 1][j] = Math.max(memo[i & 1][j], value[i] + memo[(i - 1) & 1][j - weight[i]]);
            }
        }
        return memo[(weight.length - 1) & 1][capcity];
    }

最佳

在此深入可以发现,每一行数据只依赖上一行数据的左边数据,所以我们可以将数据存储实现在一行上。

	//时间复杂度为O(C * n) 其中C为背包容量,n为石头个数
    //空间复杂度为O(C) 其中C为背包容量
    private int findRes5(int[] weight, int[] value, int capcity) {
        if(weight.length != value.length) return 0;
        if(capcity <= 0)return 0;
        if(weight.length == 0)return 0;
        //行:是否考虑该位置的石头 0:只考虑第一个  1:考虑1  n:考虑n
        //列:背包剩余容量
        int memo[] = new int[capcity + 1];

        //base case
        for(int i = 0; i <= capcity; i ++) {
            //只考虑第一个,只要容量满足,就可以放入
            if(weight[0] <= i)
                memo[i] = value[0];
        }
        //右边数据依赖于左边数据,所以只要开辟O(C)空间
        for(int i = 1; i < weight.length; i ++) {
            for(int j = capcity; j >= weight[i]; j --) {
                memo[j] = Math.max(memo[j], value[i] + memo[j - weight[i]]);
            }
        }
        return memo[capcity];
    }

测试main函数

	public static void main(String[] argc){
        int[] weight = new int[]{1, 2, 3};
        int[] value = new int[]{6, 10, 12};
        int res = new Pakage01().findRes(weight, value, 5);
        System.out.println(res);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值