动态规划经典——背包问题

题目


给你⼀个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i] ,价值为 val[i] ,现在让你⽤这个背包装物品,最多能装的价值是多少?

举个简单的例⼦,输⼊如下:
N = 4, W = 7
wt = [2 , 3 , 4 , 5]
val = [3 , 4 , 5 , 7]

算法返回:10 ,解释:选择重量为2和5的收益最大,为10.

 

分析

背包问题难点在于构建dp数组,

int[][] dp = new int[N + 1][W + 1];

了解动态规划的,大多数人都可以想到,这样定位dp数组,但是总是无法理解数组中N的含义。至少这个问题我困惑了很久。

N表示前n个元素

W表示背包的总重量,

dp表示前N个元素在背包可容量总重量为W时的最大收益

 

为什么这里N要表示前N个元素

翻看了很多文章,大多数文章没有讲清楚这个点。为什么这样构建dp。

1.假设现在N表示选择物品的数量

那么dp[1][4]则表示选择一个物品,总重量不超过4的最大收益,即为dp[1][4]=5;

这样选择也可以实现,只是复杂度更高

每次在选择的时候我们需要比较选择哪个物品更优,且记录下哪些物品已经被选择过。过程较为繁琐。

繁琐就意味着状态转移方程难构建

 

N表示前n个元素

如果表示前n个元素,我们可以看下,又怎么方便了。这种表示让计算更方便了,但是确实增大了理解难度

现在,dp[1][4]则表示选择前1个物品,总重量不超过4的最大收益,即为dp[1][4]=3(只能选择第一个);

此时,dp状态转移方程也就只是放和不放的问题

当W<w(i)时,该物品已经放不下,所以此时
dp[i][w] = dp[i-1][w](不装入第i个元素的最大收益);

当W>=w(i)时,该物品可以放不下,
所以此时

dp[i - 1][w - wt[i-1]] + val[i-1]为装入第i个元素的最大收益;

dp[i - 1][w]为不装入第i个元素的最大收益;

                    // 装⼊或者不装⼊背包,择优
                    dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
                            dp[i - 1][w]);
                            dp[i - 1][w]);
注意,这种表示方法,就需要我们的状态转移,是由前往后推的,因为后面会依赖于前面的结果

1.所以有了第一层for循环,表示考虑第一个元素要不要放,第二个元素要不要放,第N个元素要不要放

//第一层遍历,第几个元素
        for (int i = 1; i <=N; i++) {
        ......
}

2. 还有一个变量为重量,所以需要还有一个w的遍历

 for (int i = 1; i <=N; i++) {
            //第二层遍历,重量
            for (int  w= 1; w <= W; w++) {
    .......
    }
}

3. 结合上面的比较分析,有了以下的代码

//第一层遍历,第几个元素
        for (int i = 1; i <=N; i++) {
            //第二层遍历,重量
            for (int  w= 1; w <= W; w++) {
                //如果当前背包可放入重量小于wt元素的重量,则无法放入背包
                //此时dp[i][j]=dp[i-1][j];
                if (w  < wt[i - 1]) {
                    dp[i][w] = dp[i-1][w];
                }else{
                    // 装⼊或者不装⼊背包,择优
                    dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
                            dp[i - 1][w]);
                }
            }
        }

 

代码

public class PackDemo {


    public static void main(String[] args) {
        int N = 4;
        int W = 7;
        int[] wt = { 2 , 3 , 4 , 5};
        int[] val = { 3 , 4 , 5 , 7};

//
//        int[] wt = {5, 2 , 3 , 4};
//        int[] val = { 2,3 , 4 , 5 };
        getMaxVal(N, W, wt, val);

        System.out.println("-----------------------");


    }

    /*
    0-1背包问题,每个东西只能放一次
     */
    private static int getMaxVal(int N, int W, int[] wt, int[] val) {
        //构建dp数组,变化的维度为n和w,注意此处的N为前N个元素
        int[][] dp = new int[N + 1][W + 1];
        //第一层遍历,数量
        for (int i = 1; i <=N; i++) {
            //第二层遍历,重量
            for (int  w= 1; w <= W; w++) {
                //如果当前背包可放入重量小于wt元素的重量,则无法放入背包
                //此时dp[i][j]=dp[i-1][j];
                if (w  < wt[i - 1]) {
                    dp[i][w] = dp[i-1][w];
                }else{
                    // 装⼊或者不装⼊背包,择优
                    dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1],
                            dp[i - 1][w]);
                }
            }
        }
//打印数组结果查看下
        for (int[] ints : dp) {
            for (int anInt : ints) {
                System.out.print(anInt + " ");
            }
            System.out.println();
        }
        return dp[N][W];
    }
}

 

疑问

同样的参数,如果输入N=2,W=7,得到的结果是7,为什么不是重量2加上重量5的收益10?

请注意,这是一个思维误区,至少我陷入过这个误区。仔细观察我们的dp是定义的什么内容,n表示的是前N个元素,而不是所有元素。所于得到的结果表示的是前2个元素在重量为7的背包下可以得到的最大收益。

在使用时,也请注意,N的大小要等于数组的长度,才可以这样使用

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值