LintCode背包问题总结

背包问题是动态规划的一种题型,它的特点如下:

特点: 
1. 用值作为dp维度
2. dp过程就是填写矩阵
3. 可以用滚动数组进行优化

有个背包问题九讲的链接推荐:背包问题九讲

92. Backpack

Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack? 要求返回背包最多能容纳的大小是多大。

state: 
    f[i][j] 前i个物品,取出一些能否组成和为j
function:
    f[i][j] = 
        1. f[i - 1][j - a[i]] // 能放下第i个物品,那么要看除掉第i 个物品剩下的容量 j - a[i]时候与i - 1个物品的情况
        2. f[i - 1][j] // 若前i-1个物品就能组成大小为j
Intialize:
    f[0][0] = true;
Result:
    检查所有的f[n][j] (j = 0, 1, 2,..., n)

时间复杂度为O(m * n)

    public int backpack(int capacity, int[] A) {
        // state dp[m][n]: if it can fill the capacity n from the first m items
        boolean dp[][] = new boolean[A.length + 1][capacity + 1];
        // initialize
        for (int i = 0; i <= A.length; i++) {
            for (int j = 0; j <= capacity; j++) {
                dp[i][j] = false;
            }
        }
        dp[0][0] = true;
        // dp function
        for (int i = 1; i <= A.length; i++) {
            for (int j = 0; j <= capacity; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= A[i - 1] && dp[i - 1][j - A[i - 1]]) {
                    dp[i][j] = true;
                }
            }
        }
        // result
        for (int i = capacity; i >= 0; i--) {
            if (dp[A.length][i]) {
                return i;
            }
        }
        return 0;
    }
仔细观察,这道题可以用滚动数组优化,从而优化了空间复杂度:

    public int backpackWithRollingArray(int capacity, int[] A) {
        // state dp[m][n]: if it can fill the capacity n from the first m items
        boolean dp[][] = new boolean[2][capacity + 1];
        // initialize
        for (int i = 0; i < dp.length; i++) {
            for (int j = 0; j <= capacity; j++) {
                dp[i][j] = false;
            }
        }
        dp[0][0] = true;
        // dp function
        for (int i = 1; i <= A.length; i++) {
            for (int j = 0; j <= capacity; j++) {
                dp[i%2][j] = dp[(i - 1)%2][j];
                if (j >= A[i - 1] && dp[(i - 1)%2][j - A[i - 1]]) {
                    dp[i%2][j] = true;
                }
            }
        }
        // result
        for (int i = capacity; i >= 0; i--) {
            if (dp[A.length%2][i]) {
                return i;
            }
        }
        return 0;
    }

125. Backpack II

物品不仅有大小size,还有价值value,分别有2个数组表示每个物品的大小和价值,然后给一个大小为target的背包,这个背包里能装下的最大价值是多少。

State:    f[i][j]表示前i个物品当中选出一些物品组成容量为j的最大价值

DP Function:    f[i][j] = Max( f[i-1][j], f[i-1][j-A[i]]+Value[i]);  

Initialize:    f[0][0] = 0;

Result:    f[m][n]

时间复杂度:O(m * n)

    public int backPackII(int n, int[] A, int V[]) {
        int m = A.length;
        // state
        int[][] dp = new int[m + 1][n + 1];
        // initialize
        dp[0][0] = 0;
        // dp function
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                dp[i][j] = Math.max(dp[i][j], dp[i-1][j]);
                if (A[i - 1] <= j) {
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j-A[i-1]]+V[i-1]);
                }
            }
        }
        // result
        return dp[m][n];
    }


440. Backpack III

这道题是上题Backpack II的变种,区别就每种item可以重复的选择,基本思路跟上题一样,只不过由于可以重复,所以在最内层循环还要再加一层while循环用于遍历枚举重复的组合。时间复杂度是O(m * n * k)。

    public int backPackIII(int[] A, int[] V, int n) {
        int m = A.length;
        // state
        int[][] dp = new int[m + 1][n + 1];
        // initialize
        dp[0][0] = 0;
        // dp function
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                int k = 0;
                while (A[i - 1] * k <= j) {
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j-A[i-1]*k]+V[i-1]*k);  
                    k++;
                }
            }
        }
        // result
        int res = 0;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                res = Math.max(res, dp[i][j]);
            }
        }
        return res;
    }

562. Backpack IV

给定一些物品数组和一个目标值,问有多少种可以组成目标的组合数,比如给定物品数组 [2,3,6,7] 和目标值 7, 那么就有2种可能:[7] 和 [2, 2, 3]。所以返回2。这道题也可以这样描述:给1,2,5,10硬币无数多个,请问凑80元的方案总数。

State:
    dp[m][n] 前m种硬币凑成n元的方案数量
DP Function:
    dp[m][n] = dp[m - 1][n] + dp[m - 1][n - A[m] * 1] + dp[m - 1][n - A[m] * 2] + dp[m - 1][n - A[m] * 3] + ...
Initialize:
    dp[0][0] = 1
result:
    dp[m][n]

时间复杂度是O(m * n * k)

    public int backpack(int [] nums, int target) {
        // state dp[m][n]: the number of combinations that first m kinds of items form the target n
        int m = nums.length, n = target;
        int dp[][] = new int[m + 1][n + 1];
        dp[0][0] = 1;
        // dp function
        for (int i = 1; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                int k = 0;
                while (k * nums[i - 1] <= j) {
                    dp[i][j] += dp[i - 1][j - k * nums[i - 1]];
                    k++;
                }
            }
        }
        // result
        return dp[m][n];
    }

563. Backpack V

这道题是Backpack IV那道凑硬币题目的变种,唯一的区别就是现在每种类型的硬币是不可以重复的选择。每个硬币只能出现一次。

State:
    dp[m][n] 前m种硬币凑成n元的方案数量
DP Function:
    dp[m][n] = dp[m - 1][n] + dp[m - 1][n - A[m] ];
Initialize:
    dp[0][0] = 1
result:
    dp[m][n]

时间复杂度是O(m * n)

    public int backPackV(int[] nums, int target) {
        // state dp[m][n]: the number of combinations that first m kinds of items form the target n  
        int m = nums.length, n = target;  
        int dp[][] = new int[m + 1][n + 1];  
        dp[0][0] = 1;  
        // dp function  
        for (int i = 1; i <= m; i++) {  
            for (int j = 0; j <= n; j++) {
                dp[i][j] = dp[i - 1][j];
                if (nums[i - 1] <= j) {
                    dp[i][j] += dp[i - 1][j - nums[i - 1]];
                }
            }  
        }  
        // result  
        return dp[m][n]; 
    }


564. Backpack VI

给定一个包含了一些数字的数组,和一个目标值,从数组里面取数做排列,使得排列的数字的和等于target。问有多少种排列方法。数字是阔以重复取出来的。比如给定数组[1, 2, 4]和target值4。那么能得到如下的组合。总共有6种,则返回6.

[1, 1, 1, 1]
[1, 1, 2]
[1, 2, 1]
[2, 1, 1]
[2, 2]
[4]
用dp[i]表示target为i的排列数有多少种。比如以上面那个例子为例,我们可以画出如下的dp搜索图:


从而就不难写出如下代码了:

    public int backPackVI(int[] nums, int target) {
        // state
        int[] dp = new int[target + 1];
        // initialize
        dp[0] = 1;
        // dp function
        for (int i = 1; i <= target; i++) {
            for (int num: nums) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }
        }
        // result
        return dp[target];
    }

89. k Sum

从一个数组中取k个数的和为target,求有多少种组合。假如数组是[1,2,3,4], k = 2, target = 5。那么有2种解:[1,4] 和 [2,3]。

state:
    f[i][j][t]前 i 个数中取 j 个数出来组成和为 t 的组合数目
function: 
    f[i][j][t] = f[i - 1][j][t] + f[i - 1][j - 1][t - a[i - 1]] (不包括第i 个数的时候组成t的情况 + 包括第i个数的时候组成t的情况)
initialize:
    f[i][0][0] = 1
result:
    f[n][k][target]

时间复杂度:O(n * k * target)

    public int kSum(int A[], int k, int target) {
        int n = A.length;
        // state
        int[][][] dp = new int[n + 1][k + 1][target + 1];
        // initialize
        for (int i = 0; i <= n; i++) {
            dp[i][0][0] = 1;
        }
        // dp function
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                for (int l = 0; l <= target; l++) {
                    dp[i][j][l] = dp[i-1][j][l];
                    if (l >= A[i-1]) {
                       dp[i][j][l] += dp[i-1][j-1][l-A[i-1]];
                    }
                }
            }
        }
        // result
        return dp[n][k][target];
    }

91. Minimum Adjustment Cost

题意有点复杂,给定一个整型数组,调整这个数组使得每两个数之间的差值不超过给定target值。问你调整这个数组所需要的最小开销是多少。注意数组中的每个数不会超过100,这是一个非常关键的条件。因为这样的话,当前可取的值是1-100,并且与上一个值是在target的差值以内。那我们可以转换成背包问题:

State:    f[i][v] 前i个数,第i个数调整为v,满足相邻两数<=target,所需要的最小代价 
Function:    f[i][v] = min(f[i-1][v’] + |A[i]-v|, |v-v’| <= target)
Answer:    f[n][a[n]-target~a[n]+target]
时间复杂度:    O(n * A * T)

其实很简单,就是当前index为v时,我们把上一个index从1-100全部过一次,取其中的最小值(判断一下前一个跟当前的是不是abs <= target)

    public int MinAdjustmentCost(ArrayList<Integer> A, int target) {
        if (A == null || A.size() == 0) {
            return 0;
        }
        // state
        int[][] dp = new int[A.size() + 1][101];
        // dp function
        for (int i = 1; i <= A.size(); i++) {
            for (int j = 1; j <= 100; j++) {
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = 1; k <= 100; k++) {
                    if (Math.abs(k - j) > target) {
                        continue;
                    }
                    int diff = Math.abs(j - A.get(i - 1)) + dp[i-1][k];
                    dp[i][j] = Math.min(dp[i][j], diff);
                }
            }
        }
        // result
        int res = Integer.MAX_VALUE;
        for (int i = 1; i <= 100; i++) {
            res = Math.min(res, dp[A.size()][i]);
        }
        return res;
    }

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值