【背包问题系列】一、经典背包问题

593 阅读3分钟

【背包问题系列】一、经典背包问题

本系列是在学习宫水三叶大大的微信公众号-背包问题相关后整理所得,如有侵犯请告知

[博客链接]

一个菜🐔的学习之路

经典背包问题

[题目描述]

有values.length件物品和一个容量n的背包。每件物品有且只有一件。

第 件物品的重量是**weights[i]**,价值是**values[i]**。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

示例 1:


输入: V = 4, values = [4,2,3], weights = [4,2,3]
输出: 4
解释: 只选第一件物品,可使价值最大。


示例 2:


输入: V = 5, values = [4,2,3], weights = [4,2,3]
输出: 5
解释: 不选第一件物品,选择第二件和第三件物品,可使价值最大。

[思路以及题目分析]

  • 面对dp问题我们最开始不能直接想到dp方程的时候,可以考虑通过DFS|递归来确定递归方程

  • 根据本题题目分析可以得出递归方程int dfs(int n,int i, int[] values, int[] weights )

  • n(当前容量)、i(当前物品)

  • 因此dp方程可以确定由两个可变参数生成

  • dp[i][j]表示在使用前i个物品,在最大重量j的情况,获得的最高价值

  • i = 0时,需要初始化dp数组dp[0][j]

  • 动态方程推导可以按照如下思路分析

  • 面对i物品是否取值,需要判断取**(dp[i][j-weight[i]] + val[i])**或者不取(dp[i-1][j])谁的价值更高

  • dp[i][j] = Math.max(dp[i-1][j],dp[i][j-weights[i]] + values[i])
    
public int getMaxValue(int[] value, int[] weights, int n) {
            int[][] dp = new int[value.length][n + 1];
            //边界条件
            for (int i = 0; i < n; i++) {
                dp[0][i] = (weights[0] <= i) ? value[0] : 0;
            }
            for (int i = 1; i < value.length; i++) {
                int val = value[i];
                for (int j = 1; j <= n; j++) {
                    if (j >= weights[i]) {
                        //取选当前物品和不选当前物品的最大值
                        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + val);
                    }
                }
            }
            return dp[value.length - 1][n];
        }

时间复杂度O(N*C) N代表物品数量,C代表最大重量


优化一:滚动数组以及动态规划的结合优化存储空间

  • 根据上述递归方程可以看到dp[i][j]只和dp[i-1][j]有关,这样的动态规划问题可以通过滚动数组来进行dp方程求解
  • 定义**dp[2][j]**存储前一位的最大价值求解和当前位最大价值求解
  • 至于如何遍历两个元素我们可以通过奇偶性来平移遍历
 public int getMaxValue(int[] values, int[] weights, int n) {
            int[][] dp = new int[2][n + 1];
            for (int i = 0; i < n; i++) {
                dp[0][i] = (weights[0] <= i) ? values[0] : 0;
            }
            for (int i = 1; i < values.length; i++) {
                for (int j = 0; j <= n; j++) {
                    //不选择当前物品
                    int tempF = dp[(i - 1) & 1][j];
                    //选择当前物品
                    int tempT = j >= weights[i] ? dp[(i - 1) & 1][j - weights[i]] + values[i] : 0;
                    dp[i & 1][j] = Math.max(tempF, tempT);
                }
            }
            return dp[(values.length - 1) & 1][n];
        }

时间复杂度O(N*C) N代表物品数量,C代表最大重量


优化二:明确依赖关系,再度进行空间优化

  • 根据上述优化可以发现,当前的最大价值仅与两个确定的值有关
  • 也就是dp[i][j]仅与dp[i-1][j] 和**dp[i][j-weights[i]]**两个值
  • image-20210613173830634
public int getMaxValue(int[] values, int[] weights, int n) {
            int[] dp = new int[n + 1];
            for (int i = 0; i < values.length; i++) {
                for (int j = n; j >= weights[i]; j--) {
                    // 不选该物品
                    int x = dp[j];
                    // 选择该物品
                    int y = dp[j - weights[i]] + values[i];
                    dp[j] = Math.max(x, y);
                }
            }
            return dp[n];
        }