[动态规划](烦人的背包)背包问题

文章目录

  • 背包问题:
    • 思路:
      • 动态规划:
    • 代码:
      • 代码优化:
  • [背包问题 II](https://www.lintcode.com/problem/125/)
    • 思路:
    • 代码
  • [背包问题 III](https://www.lintcode.com/problem/440/)
    • 思路:
    • 代码:
  • [背包问题 IV](https://www.lintcode.com/problem/562/)
    • 思路:
      • 代码:
  • [背包问题 V](https://www.lintcode.com/problem/563/)
    • 思路

背包问题:

描述
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
你不可以将物品进行切割。
样例
样例 1:
输入: [3,4,8,5], backpack size=10
输出: 9
样例 2:
输入: [2,3,5,7], backpack size=12
输出: 12

思路:

对于上面的问题,我们进行分析,以样例一为例:
背包大小为10,我们最多能装多满呢?
如果我们使用贪心算法的话,也就是说,只要背包有足够的空间能够容纳该物品,我们就放入背包;
第一个物品放入,背包还有7容量;
第二个物品放入,背包还有3容量;
第三个物品放入时,背包剩余容量不够了:这时候,我们需要考虑是不是要放入,如果放入背包的话,我们需要取出那些物品才能够将背包放的更满呢?
经过分析,我们可以判断,我们需要保存一些状态信息,来帮助我们判断是否放入物品到背包中及取出那些物品在放入背包中可以使背包更满,这些条件贪心算法是不能够完成的,我们刚才提到保存状态信息,所以一定有状态转移,所以我们使用动态规划是能够解决这个问题的

动态规划:

状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,能装的最大状态
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +A[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

代码:

int backPack(int m, vector<int> &A) {
     
        // write your code here
        int sizeA=A.size();
        vector<vector<int>>  dp(sizeA+1,vector<int>(m+1));
        for(int i=1;i<=sizeA;++i)
        {
     
            for(int j=1;j<=m;++j)
            {
     
                if(A[i-1]>j)
                {
     
                    //物品大于背包容量,一定装不下
                    dp[i][j]=dp[i-1][j];
                }
                else
                {
     
                    //物品小于背包容量,一定能够装下物品
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-A[i-1]]+A[i-1]);
                }
            }
        }
        return dp[sizeA][m];
    }

代码优化:

在上面的代码中用二维数组存储状态,但是有些空间的浪费,因为我们每次都是一行一行的进行状态转移,因此我们可以将二维数组使用以为数组进行代替,这样可以节省空间,但是在进行状态转移的时候,我们大容量背包的状态一定是要从小容量的状态推到出来的,因此在循环的时候,我们要从大容量向小容量循环;优化代码如下:

  int backPack(int m, vector<int> &A) {
     
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1);
        for(int i=1;i<=sizeA;++i)
        {
     
            for(int j=m;j>=A[i-1];--j)
            {
     
                    dp[j]=max(dp[j],dp[j-A[i-1]]+A[i-1]);
            }
        }
        return dp[m];
    }

背包问题 II

描述
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?
A[i], V[i], n, m 均为整数
你不能将物品进行切分
你所挑选的要装入背包的物品的总大小不能超过 m
每个物品只能取一次
样例
样例 1:
输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4]
输出: 9
解释: 装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9
样例 2:
输入: m = 10, A = [2, 3, 8], V = [2, 5, 8]
输出: 10
解释: 装入 A[0] 和 A[2] 可以得到最大价值, V[0] + V[2] = 10

思路:

解决此题,照旧动态规划:
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,装入背包的最大价值
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +V[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

对于上面的思想,我们可以使用以为数组进行空间优化,思想和背包问题类似;

代码

int backPackII(int m, vector<int> &A, vector<int> &V) {
     
        // write your code here
        vector<int> dp(m+1,0);
        int sizeA=A.size();
        for(int i=1;i<=sizeA;++i)
        {
     
            for(int j=m;j>=A[i-1];--j)
            {
     
                    dp[j]=max(dp[j],dp[j-A[i-1]]+V[i-1]);
               
            }
        }
        return dp[m];
    }

背包问题 III

描述
给定 n 种物品, 每种物品都有无限个. 第 i 个物品的体积为 A[i], 价值为 V[i].
再给定一个容量为 m 的背包. 问可以装入背包的最大价值是多少?
不能将一个物品分成小块.
放入背包的物品的总大小不能超过 m.
样例
样例 1:
输入: A = [2, 3, 5, 7], V = [1, 5, 2, 4], m = 10
输出: 15
解释: 装入三个物品 1 (A[1] = 3, V[1] = 5), 总价值 15.
样例 2:
输入: A = [1, 2, 3], V = [1, 2, 3], m = 5
输出: 5
解释: 策略不唯一. 比如, 装入五个物品 0 (A[0] = 1, V[0] = 1).

思路:

不用说,这个题目依旧动态规划,不过与上两道题目不同的是 每个物品有无限次,我们需要对此进行处理;
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,装入背包的最大价值
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,是可以放入背包的,但是我们面临着两种选择,放或者不放
不放的话 dp[i][j]=dp[i-1][j]
放的话,背包至少要剩余物品空间的大小,因此我们需要找到这个大小的背包 j-A[i];
dp[i][j]= dp[i-1][j-A[i]] +V[i];
上面的两种情况下,我们只需要取最大的值,满足我们的状态定义
如果我们j为0,肯定放入的大小为0
如果i为0,表示没有放入任何物品,此时放入的大小也一定为0;

对于上面的思想,我们可以使用以为数组进行空间优化,思想和背包问题类似;

上面就是我们以往的思想,因为每个物品有无限个,因此在放入背包物品的时候,我们不再仅有一种选择,而是由多种选择:
dp[i][j]= dp[i-1][j-A[i]] +V[i];
dp[i][j]= dp[i-1][j-2*A[i]] +2 * V[i];
dp[i][j]= dp[i-1][j-3 * A[i]] +3 * V[i];
。。。。。。
直至背包装不下为止,背包三的改动也仅仅局限与此;

代码:

int backPackIII(vector<int> &A, vector<int> &V, int m) {
     
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1,0);

        for(int i=1;i<=sizeA;++i)
        {
     
            for(int j=m;j>=A[i-1];--j)
            {
     
                 int count=1;
                 while(count*A[i-1]<=j)
                 {
     
//我们可以采用一个循环实现无限次放入物品,直至放不下的情况,当然这是一种最直接的方法
                    dp[j]=max(dp[j],dp[j-count*A[i-1]]+count*V[i-1]);
                    ++count;
                 }        
            }
        }
        return dp[m];
    }
int backPackIII(vector<int> &A, vector<int> &V, int m) {
     
        // write your code here
        int sizeA=A.size();
        vector<int>  dp(m+1,0);
        for(int i=1;i<=sizeA;++i)
        {
     
            for(int j=A[i-1];j<=m;++j)
            {
     
            //与前面不同,用小状态更新后面的状态可以避免多次使用循环,提高了效率
                if(dp[j-A[i-1]]+V[i-1]>dp[j])
                {
     
                    dp[j]=dp[j-A[i-1]]+V[i-1];
                }
            }
        }
        return dp[m];
    }

背包问题 IV

描述
给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次
样例
样例1
输入: nums = [2,3,6,7] 和 target = 7
输出: 2
解释:
方案有:
[7]
[2, 2, 3]
样例2
输入: nums = [2,3,4,5] 和 target = 7
输出: 3
解释:
方案有:
[2, 5]
[3, 4]
[2, 2, 3]

思路:

不用多说,依旧动态规划
状态定义:
我们定义一个二维数组dp[ i][ j ] 表示前i个物品放入j大小的背包中,放满背包的方案数
状态转移方程:
如果第i个物品的空间大于背包的空间,无论如何 该物品是不能放入背包中的 因此dp[ i][ j] =dp[i-1][j];
如果第i个物品的空间不大于背包的空间,物品是可以放入背包的,
但是如果要放满的话
dp[i][j]= dp[i-1][j-A[i]] ;
dp[i][j]= dp[i-1][j-2 * A[i]] ;
dp[i][j]= dp[i-1][j-3 * A[i]] ;
。。。
知道不能存放为止;
由于是求放满背包的数量,因此我们需要将上面的结果相加
同样,我们可以进行空间优化

代码:

int backPackIV(vector<int> &nums, int target) {
     
        // write your code here
        int size=nums.size();
        vector<int> dp(target+1,0);
        dp[0]=1;//如果背包大小为0的话,我们仅有一种装满的方法,即不妨任何东西
        for(int i=1;i<=size;++i)
        {
     
            for(int j=nums[i-1];j<=target;++j)
            {
     
                    dp[j]+=dp[j-nums[i-1]];
            }
        }
        return dp[target];
    }

背包问题 V

描述
给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次
样例
给出候选物品集合 [1,2,3,3,7] 以及 target 7
结果的集合为:
[7]
[1,3,3]
返回 2

思路

本体的解决思路依旧是动态规划,需要注意的地方是每个物品只能使用一次,且物品重量有相同的可能,因此如果从小状态到大状态的转变时,有可能会出现冗余的情况,避免冗余结果的产生,我们需要从大状态转变为小状态

int backPackV(vector<int> &nums, int target) {
     
          // write your code here
        
        int size=nums.size();
        //进行部分解的优化,如果数组中素有物品极爱起来,都不能够装满背包,则一定装不满背包
        int sum=0;
        for(int i=0;i<size;++i)
        {
     
             sum+=nums[i];
        }
        if(sum<target)
        {
     
            return 0;
        }
        vector<int> dp(target+1,0);
        dp[0]=1;//如果背包大小为0的话,我们仅有一种装满的方法,即不妨任何东西
        for(int i=1;i<=size;++i)
        {
     
            for(int j=target;j>=nums[i-1];--j)
            {
     
                    dp[j]+=dp[j-nums[i-1]];
            }
        }
        return dp[target];
    }

如有错误,欢迎评论指正!!!
点击题目,即可转向OJ

你可能感兴趣的:(代码练习,动态规划,算法)