动态规划-背包问题降维

写在最前面, 这些方法肯定不是我原创的,也是看的别人的思路,
然后自己理解并且实现了
https://blog.csdn.net/iihtd/article/details/51611810?utm_source=blogxgwz2
这个里面讲了背包问题,以及背包的衍生问题,还是不错的,
https://blog.csdn.net/stack_queue/article/details/53544109
这个是著名的背包九讲,讲的很好

0-1 背包问题:

0-1 背包问题解决的是物品只能够选择1次要不就不选,在背包能够装得下的前提下面,能够保证装的价值最大:
状态转移方程 f[i][j] = max{ f[i-1][j] , f[i-1][j – wi] +vi }; 0≤j – wi≤j
用二维的空间来表示是比较简单的
一维表示:f[v] 表示 f[i][v] , 那么由于上面的这个公式: f[i][j] = max{ f[i-1][j] , f[i-1][j – wi] +vi };
可以发现f[i][j] 的值只是和f[j-1][j] , 和f[i-1][j-wi] ,如果我们写出如下的代码:

for(int i = 0 ; i < n ; i++){
        for(int j = m ; j >=0  ; j--){
        vec[j] = max(vec[j] , vec[j-w[i]]+v[i] );
        }
     }

那么对于i = N 的情况,我们计算 vec [j] 时, 由于j-wi < j ,那么vec[j-w[i]]的值会在vec[j]后参与计算,所以,在计算vec[j ] 的时候,我们先读取到的vec[j] 实际上是vec[N-1][j] , 读取到的 vec[j-w[i]]实际上是vec[N-1][j-w[i]],所以可以用上面的公式进行简化。

现在是0-1背包问题的二维代码实现:

/** 
int[] w 表示物品的重量数组
int[] v 表示物品的价值
int weight 表示背包能够装入的重量
*/
public static int backpack01(int[] w, int[] v, int weight) {
        int n = w.length + 1;  //多少个物品
        int[][] temp = new int[n][weight + 1];
        for (int i = 0; i < n; i++) {
            temp[i][0] = 0;//代表背包承受0重量时,背包的价值
        }
        for (int j = 0; j < weight + 1; j++) {
            temp[0][j] = 0;//代表背包存放0个物品时,背包的价值
        }
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < weight + 1; j++) {
                if (j > w[i]) {  //这样背包才能放下这个物品
                    temp[i][j] = Math.max(temp[i - 1][j], temp[i - 1][j - w[i]] + v[i]);
                } else {
                    temp[i][j] = temp[i - 1][j];
                }
            }
        }
        return temp[n-1][weight];
    }

现在是0-1背包问题的一维代码实现
当只有第一个物品的时候,相当于是往背包里面装第一个东西,
第二遍是有两个物品的时候了,相当于在有第一个物品的基础上,
放第二个物品来使背包价值最大化

/** 
int[] w 表示物品的重量数组
int[] v 表示物品的价值
int weight 表示背包能够装入的重量
*/
    public static int backpack01youhua(int[] w, int[] v, int weight) {
        int[] f = new int[weight + 1];
        for (int i = 0; i < v.length; i++) {
            for (int j = weight; j >= w[i]; j--) {
                f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
            }
        }
        return f[weight];
    }

完全背包问题

完全背包问题中每个物品都可以选择无限多次
for (int j = 0; j <=weight; j++)
所以这个地方不同于01背包从背包里面扔东西,
这个是往背包里面一步一步的加

/** 
int[] w 表示物品的重量数组
int[] v 表示物品的价值
int weight 表示背包能够装入的重量
*/
    public static int completebackpack(int[] w, int[] v, int weight) {
        int[] f = new int[weight + 1];
        for (int i = 0; i < v.length; i++) {
            for (int j = 0; j <=weight; j++) {
                f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
            }
        }
        return f[weight];
    }

多重背包问题

部分物品的数量有限,那就和01背包问题一样

/** 
int[] w 表示物品的重量数组
int[] v 表示物品的价值
int[] num 表示每个物品的数量
int weight 表示背包能够装入的重量
*/
      public static int completebackpack(int[] w, int[] v, int[] num, int weight) {
        int[] f = new int[weight + 1];
        for (int i = 0; i < v.length; i++) {
            for (int j = weight; j >= w[i]; j--) {
                for (int k = 1; k <= num[i]; k++) {
                    if (j - k * w[i] > 0 && f[j - k * w[i]] + k * w[i] > f[j]) {
                        f[j] = f[j - k * w[i]] + k * w[i];
                    }
                }
            }
        }
        return f[weight];
    }

背包衍生问题: 硬币凑整数 即子集合加总问题

硬币问题和完全背包问题类似,原题是这样的:有1分,2分,5分,10分四种硬币,每种硬币数量无限,给定n分钱,有多少中组合可以组成n分钱?
采用动态规划的思路,f(i,j)表示的是可以取的0,1,2…i种硬币的情况下,凑够j元一共有多少种方法.递推公式以及相应的一维如何推导为二维如下:

《动态规划-背包问题降维》 image.png

/**
int[] coins 代表有多少种类型的coin
int target 代表要凑成的数目
*/
public static int coins(int[] coins, int target) {
        int n = coins.length;
        int[] f = new int[target + 1];
        f[0] = 1;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= target; j++) {
                f[j] = f[j] + f[j - coins[i]];
            }

        }
        return f[target];
    }

附加一个衍生问题
https://www.cnblogs.com/wudongyang/p/4949557.html
0-1背包问题与子集合加总问题的近似算法
组合总和IIIhttps://blog.csdn.net/wjoker/article/details/84404478

输入两个整数值n和m,求出整数1到n之间的和为m的所有组合
https://blog.csdn.net/qq_36653505/article/details/82634774 python版本简单, java版本麻烦一些
https://blog.csdn.net/weixin_37365120/article/details/70068214

给定一个数组,从中查找是否存在两个数的和等于一个给定的x
另外借鉴了人家一种o(n)的方法,借用一个容器,在容器中查找x-arr[n]的值是否存在,存在就返回 true,不存在将这个值加入容器。
https://blog.csdn.net/chen3749102/article/details/45336371
public static boolean hasAB(int[] arr, int x){
HashSet<Integer> set=new HashSet<>();
for(int i=0;i<arr.length;i++){
if(set.contains(x-arr[i]))
return true;
else
set.add(arr[i]);
}
return false;

    原文作者:yesski
    原文地址: https://www.jianshu.com/p/918ca2aff350
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞