背包问题

1 类型

  • 布尔值
  • 计数
  • 最值
  • 单一物品
  • 无限多物品
  • 空间优化:1)滚动数组 2)

 

2 重点背包问题中,数组的大小和总承重有关

3 动态规划的四步骤:

  1. 确定状态:确定状态时根据最后一个问题和子问题
  2. 转移方程
  3. 初始条件和边界情况
  4. 计算顺序

4  描述: 你有一个背包,背包有最大承重,商店里有若干物品,都是免费拿,每个物品有重量和价值

目标:不撑爆背包的前提下

  • – 装下最多重量物品
  • – 装下最大总价值的物品
  • – 有多少种方式正好带走满满一书包物品

逐个放物品,看是否还能放入:两个关键点 :还有几个物品; 还剩多少承重

 

5 例题

1)单一物品----->最值型

http://www.lintcode.com/en/problem/backpack/

      题意:给定N个物品,重量分别为正整数A0,A1, …, AN-1, 一个背包最大承重是正整数M,最多能带走多重的物品

      例子:

输入:4个物品,重量为2, 3, 5, 7. 背包最大承重是11

输出:10 (三个物品:2, 3, 5)

 

解题思想:

一、确定状态

需要知道N个物品是否能拼出重量W (W =0, 1, …, M)

最后一步:最后一个物品(重量AN-1)是否进入背包0 1 2 … M-1 M

  • 情况一:如果前N-1个物品能拼出W,当然前N个物品也能拼出W
  • 情况二:如果前N-1个物品能拼出W- AN-1 ,再加上最后的物品AN-1 ,拼出W

子问题:要求前N个物品能不能拼出重量0, 1, …, M,需要知道前N-1个物品能不能拼出重量0, 1, …, M

状态:设f[i][w] = 能否用前i个物品拼出重量w (TRUE / FALSE)

 

二、转移方程

设f[i][w] = 能否用前i个物品拼出重量w (TRUE / FALSE)

f[i][w] = f[i-1][w] OR f[i-1][w-Ai-1]

 

三:初始条件和边界情况

初始条件:

  • f[0][0] = TRUE: 0个物品可以拼出重量0
  • f[0][1..M] = FALSE: 0个物品不能拼出大于0的重量

边界情况:

f[i-1][w-Ai-1]只能在w≥Ai-1时使用

 

四:计算顺序

  • 初始化f[0][0], f[0][1], …, f[0][M]
  • 计算前1个物品能拼出哪些重量:f[1][0], f[1][1], …, f[1][M]
  • 计算前2个物品能拼出哪些重量:f[2][0], f[2][1], …, f[2][M]
  • 计算前N个物品能拼出哪些重量:f[N][0], f[N][2], …, f[N][M]

 

代码:

public static int test3(int[] nums,int C){
    if(nums.length==0||nums==null)
        return 0;
    int length=nums.length;
    boolean[][] f=new boolean [length+1][C+1];
   int maxWeight=0;
   f[0][0]=true;
    for(int i=1;i<=C;i++){
        f[0][i]=false;
    }
   for(int i=1;i<=length;i++){
       f[i][0]=true;
       for(int j=1;j<=C;j++){
            if(j>=nums[i-1]){
                f[i][j]=f[i-1][j]||f[i-1][j-nums[i-1]];
            }else{
                f[i][j]=f[i-1][j];
            }
       }
   }
   for(int i=C;i>=0;i--){
       if(f[length][i]){
           maxWeight=i;
           break;
       }
   }

    return maxWeight;
}

时间复杂度(计算步数):O(MN),空间复杂度(数组大小):优化后可以达到O(M)

 

小结

• 要求不超过Target时能拼出的最大重量

• 记录前i个物品能拼出哪些重量

• 前i个物品能拼出的重量:

– 前i-1个物品能拼出的重量

– 前i-1个物品能拼出的重量+第i个物品重量Ai-1

 

2)单一物品----->计数型

http://www.lintcode.com/problem/backpack-v/

       题意:给定N个正整数:A0,A1, …, AN-1,一个正整数Target。求有多少种组合加起来是Target,每个Ai只能用一次

      例子:

输入:A=[1, 2, 3, 3, 7], Target=7

输出:2 (7=7, 1+3+3=7)

 

解题思路

一、确定状态

需要知道N个物品有多少种方式拼出重量W (W =0, 1, …, Target)

最后一步:第N个物品(重量AN-1)是否进入背包

情况一:用前N-1个物品拼出W

情况二:用前N-1个物品能拼出W- AN-1 ,再加上最后的物品AN-1 ,拼出W

情况一的个数+情况二的个数=用前N个物品拼出W的方式

子问题:要求前N个物品有多少种方式拼出重量0, 1, …, Target,需要知道前N-1个物品有多少种方式拼出拼出重量0, 1, …, Target

 

二、转移方程

设f[i][w] = 用前i个物品有多少种方式拼出重量w

f[i][w] = f[i-1][w] + f[i-1][w-Ai-1]

 

三、初始条件和边界情况

初始条件:

– f[0][0] = 1: 0个物品可以有一种方式拼出重量0

– f[0][1..M] = 0: 0个物品不能拼出大于0的重量

边界情况:

– f[i-1][w-Ai-1]只能在w≥Ai-1时使用

 

四、计算顺序

  • 初始化f[0][0], f[0][1], …, f[0][Target]
  • 计算前1个物品有多少种方式拼出重量:f[1][0], f[1][1], …, f[1][Target]
  • 计算前N个物品有多少种方式拼出重量:f[N][0], f[N][2], …, f[N][Target]
  • 答案是f[N][Target]

 

代码:

//对空间进行优化:从后往前遍历,新生成的f[j]可以覆盖前面用到的f[j];
public static int test3(int[] A,int m){
    int length=A.length;
    if(length<=0)
        return 0;
    int[] f=new int[m+1];
    f[0]=1;
    for(int i=1;i<=length;i++){
      for(int j=m;j>=0;j--){
          //  f[i][j]=f[i-1][j]+f[i-1][i-A[i-1]];
          if(j-A[i-1]>=0)
            //如果为假,就直接等于前面的f[j];
              //如果为真:f[i][j]=f[i-1][j]+f[i-1][i-A[i-1]]----> f[j]+=f[j-A[i-1]];
              //这句代码有两个作用,一是判断新生成的f[j],而是用新生成的f[j]来代替老的f[j],实现数组的动态更新
              f[j]+=f[j-A[i-1]];
      }
    }
    return f[m];
}

时间复杂度(计算步数):O(N*Target),空间复杂度(数组大小):优化后可以达到O(Target)

 

3)无限多物品----->计数型

http://www.lintcode.com/problem/backpack-vi/

       题意:给定N个正整数:A0,A1, …, AN-1, 一个正整数Target,求有多少种组合加起来是Target,每个Ai可以用多次

       例子:

输入:A = [1, 2, 4], Target = 4

输出:6(1+1+1+1=4, 2+2=4, 1+1+2=4, 1+2+1=4, 2+1+1=4, 4=4)

 

解题思路

一:确定状态

最后一步:最后一个物品的重量是多少

关键点1:任何一个正确的组合中,所有物品总重量是Target

关键点2:如果最后一个物品重量是K,则前面的物品重量是Target-K

  • 如果最后一个物品重量是A0, 则要求有多少种组合能拼成Target – A0
  • 如果最后一个物品重量是A1, 则要求有多少种组合能拼成Target – A1
  • 如果最后一个物品重量是AN-1, 则要求有多少种组合能拼成Target – AN

子问题:原问题要求有多少种组合能拼成Target,变成有多少种组合可以拼成Target – A0,...Target – AN.

 

二:转移方程

设f[i] = 有多少种组合能拼出重量i

f[i] = f[i-A0] + f[i-A1] +…+ f[i-AN-1]

 

三:初始条件和边界情况

初始条件:f[0] = 1,有1种组合能拼出重量0 (什么都不选)

边界情况:如果i

 

四:计算顺序

  • 设f[i] = 有多少种组合能拼出重量i
  • f[i] = f[i-A0] + f[i-A1] +…+ f[i-AN-1]
  • f[0] = 1
  • 计算f[1], f[2], …, f[Target]
  • 结果为f[Target]

 

代码:

public static int test(int[] nums, int target ){
    int length=nums.length;
    if(nums.length==0)
        return 0;
    int counts[]=new int[target+1];
    counts[0]=1;

  for(int i=1;i<=target;i++){
      counts[i]=0;
      for(int j=0;j=0)
              counts[i]+=counts[i-nums[j]];
      }
  }
  return counts[target];
}

时间复杂度(计算步数): O(N*Target),空间复杂度:O(Target)

 

4)单个物品----->价值最大型

http://www.lintcode.com/problem/backpack-ii/

        题意:给定N个物品,重量分别为正整数A0,A1, …, AN-1,价值分别为正整数V0,V1, …, VN-1, 一个背包最大承重是正整数M,最多能带走多大价值的物品

       例子:

输入:4个物品,重量为2, 3, 5, 7,价值为1, 5, 2, 4. 背包最大承重是11

输出:9 (物品一+物品三,重量3+7=10,价值5+4=9)

 

解题思路

一:确定状态

和前一题类似,需要知道N个物品,是否能拼出重量W (W =0, 1, …, M),对于每个重量W,最大总价值是多少

最后一步:最后一个物品(重量AN-1, 价值VN-1)是否进入背包

  • 选择一:如果前N-1个物品能拼出W,最大总价值是V,前N个物品也能拼出W并且总价值是V
  • 选择二:如果前N-1个物品能拼出W- AN-1,最大总价值是V,则再加上最后一个物品(重量AN-1, 价值VN-1),能拼出W,总价值是V+VN-1

子问题:要求前N个物品能不能拼出重量0, 1, …, M,以及拼出重量W能获得的最大价值,需要知道前N-1个物品能不能拼出重量0, 1, …, M,以及拼出重量W能获得的最大价值

 

二:转移方程

设f[i][w] = 用前i个物品拼出重量w时最大总价值(-1表示不能拼出w)

f[i][w] = max{f[i-1][w], f[i-1][w-Ai-1] + Vi-1 | w≥Ai-1 且f[i-1][w-Ai-1] ≠-1}

 

三:初始条件和边界情况

初始条件:

  • f[0][0] = 0: 0个物品可以拼出重量0,最大总价值是0
  • f[0][1..M] = -1: 0个物品不能拼出大于0的重量

边界情况:f[i-1][w-Ai-1]只能在w≥Ai-1,并且f[i-1][w-Ai-1] ≠-1时使用

 

四:计算顺序

  • 初始化f[0][0], f[0][1], …, f[0][M]
  • 计算前1个物品拼出各种重量的最大价值:f[1][0], f[1][1], …, f[1][M]
  • 计算前N个物品拼出各种重量的最大价值:f[N][0], f[N][2], …, f[N][M]
  • 答案:max0<=j<=M{f[N][j] | f[N][j] ≠-1}

 

代码:

public static int test(int[] weight,int[] values,int m){
    int length=weight.length;
    if(length==0)
        return 0;
    int nums[][]=new int[length+1][m+1];
    nums[0][0]=0;
    for(int i=1;i<=m;i++){
        nums[0][i]=-1;
    }

    //可以按照combinatioSum4 这个类里面的把数组压缩,压缩成一维数组,要注意的是如果压缩就一定要倒着求;
    for(int i=1;i<=length;i++){
        nums[i][0]=0;
        for(int j=1;j<=m;j++){
            nums[i][j]=nums[i-1][j];
            if(j>=weight[i-1]&&nums[i-1][j-weight[i-1]]!=-1){
                nums[i][j]=Math.max(nums[i-1][j],nums[i-1][j-weight[i-1]]+values[i-1]);
            }
        }
    }
    int maxValue=nums[length][1];
    for(int i=2;i<=m;i++){
        if(nums[length][i]>maxValue)
            maxValue=nums[length][i];
    }
    return maxValue;
}

时间复杂度(计算步数):O(MN),空间复杂度(数组大小):优化后可以达到O(M)

 

5)无限多物品----->价值最大型

http://www.lintcode.com/problem/backpack-iii/

        题意:给定N种物品,重量分别为正整数A0,A1, …, AN-1,价值分别为正整数V0,V1, …, VN-1,每种物品都有无穷多个,一个背包最大承重是正整数M 最多能带走多大价值的物品

       例子:

输入:4个物品,重量为2, 3, 5, 7,价值为1, 5, 2, 4. 背包最大承重是10

输出:15 (3个物品一,重量3*3=9,价值5*3=15)

 

解题思路

无穷多物品的解题思路可以参考上面的计数型的解题思路,只不过这时候加上了价值,求价值的最大值。

代码:

public static int test(int[] weight,int[] values,int m){
    int length=weight.length;
    if(length==0)
        return 0;
    int[] counts=new int[m+1];
    counts[0]=0;
    int max=0;
    for(int i=1;i<=m;i++){
        counts[i]=-1;
        for(int j=0;j=weight[j]&&counts[i-weight[j]]!=-1)
                counts[i]=Math.max(counts[i],counts[i-weight[j]]+values[j]);
        }
        max=max>counts[i]?max:counts[i];
    }
    return max;
}

max=max>counts[i]?max:counts[i];

}

return max;

}

时间复杂度(计算步数):O(MN),空间复杂度O(M)

 

6 总结

可行性背包 – 题面:要求不超过Target时能拼出的最大重量,记录f[i][w]=前i个物品能不能拼出重量w

计数型背包 – 题面:要求有多少种方式拼出重量Target:记录f[i][w]=前i个物品有多少种方式拼出重量w

最值型背包 – 题面:要求能拼出的最大价值:记录f[i][w]=前i个/种物品拼出重量w能得到的最大价值

• 关键点

最后一步 • 最后一个背包内的物品是哪个,最后一个物品有没有进背包,数组大小和最大承重Target有关

• 空间优化

 

 

你可能感兴趣的:(Leetcode)