【动态规划】背包问题:完全背包,二位费用的背包问题,似包非包

文章目录

完全背包

1. 完全背包(DP 42)

完全背包

题目描述

解题思路

背包可以不装满
  • 状态表示:dp[i][j]表示前i个物品中总体积不超过j,所有选法中最大价值
  • 状态转移方程:对第 i 种物品只有两种大选择:
    • 情况 1:不选第 i 件物品:dp[i][j] = dp[i-1][j]
    • 情况 2:选至少 1 个第 i 件物品:既然已经选了 1 个,剩下空间j-v[i]依然可以继续随便选第 i 件,等价于:在前 i 种物品、容量 j-v[i] 的最优方案基础上,再加一个 i 号物品dp[i][j] = dp[i][j-v[i]] + w[i]
    • 与01背包区别:
      • 01背包:dp[i][[j] = dp[i-1][j-v[i]]+w[i]
      • 完全背包:dp[i][j] = dp[i][j-v[i]]+w[i];
    • 以上两种情况取最大值 dp[i][j] = max(dp[i-1][j],dp[i][j-v[i]]+w[i]),要先判断j-v[i]是否存在
  • 初始化:
    • i = 0时,表示不选任何物品,体积不超过j的价值,全部都是0,初始化可以省略
    • j = 0时,表示体积为0,最大价值也是0,初始化可以省略
背包必须装满
  • 状态表示:dp[i][j] 表示在前i个物品中总体积为j的最大价值,如果找不到体积为j的方案,就令dp[i][j] = -1
  • 状态转移方程:dp[i][j] = max(dp[i-1][j],dp[i][j-v[i]]+w[i]),要判断j-v[i]>0 也要判断dp[i][j-v[i]]>0,表示这种状态存在
  • 初始化:
    • i = 0:表示不选任何物品,可以凑出体积为j的最大价值,j = 0时,价值为0,j>0时没有方式,所以初始化为-1
    • j = 0:表示在前i个物品中选出体积为0的最大价值,都是0,可以省略初始化

优化:利用滚动数组进行空间优化,删除第一维度

  1. dp[i][j]会使用到i-1行和当前行的j-v[i]列,因此空间优化要从左向右填表
  2. 01背包只用到上一行的数据,使用后覆盖,因此从右向左填表

代码实现

java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int V = in.nextInt();
        int[] v = new int[n];
        int[] w = new int[n];

        for(int i = 0;i<n;i++){
            v[i] = in.nextInt();
            w[i] = in.nextInt();
        }
        //方案一
        int[][] dp1 = new int[n+1][V+1];
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=V;j++){
                dp1[i][j] = dp1[i-1][j];
                if(j-v[i-1]>=0)
                    dp1[i][j] = Math.max(dp1[i][j-v[i-1]]+w[i-1],dp1[i][j]);
            }
        }
        System.out.println(dp1[n][V]);

        //方案二
        int[][] dp2 = new int[n+1][V+1];
        for(int j = 1;j<=V;j++)
            dp2[0][j] = -1;

        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=V;j++){
                dp2[i][j] = dp2[i-1][j];
                if(j-v[i-1]>=0 && dp2[i][j-v[i-1]]>=0)
                    dp2[i][j] = Math.max(dp2[i][j-v[i-1]]+w[i-1],dp2[i][j]);
            }
        }
        System.out.println(Math.max(0,dp2[n][V]));
    }
}
  • 空间优化:
java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int V = in.nextInt();
        int[] v = new int[n];
        int[] w = new int[n];

        for(int i = 0;i<n;i++){
            v[i] = in.nextInt();
            w[i] = in.nextInt();
        }
        //方案一
        int[] dp1 = new int[V+1];
        for(int i = 1;i<=n;i++){
            for(int j = v[i-1];j<=V;j++){
                dp1[j] = Math.max(dp1[j-v[i-1]]+w[i-1],dp1[j]);
            }
        }
        System.out.println(dp1[V]);

        //方案二
        int[] dp2 = new int[V+1];
        for(int j = 1;j<=V;j++)
            dp2[j] = -1;

        for(int i = 1;i<=n;i++){
            for(int j = v[i-1];j<=V;j++){
                if( dp2[j-v[i-1]]>=0)
                    dp2[j] = Math.max(dp2[j-v[i-1]]+w[i-1],dp2[j]);
            }
        }
        System.out.println(Math.max(0,dp2[V]));
    }
}

2. 零钱兑换(LC 322)

零钱兑换

题目描述

解题思路

  • 状态表示:前i个硬币中,总和为j的最小硬币个数
  • 状态转移方程:
    • 不选i:dp[i][j] = dp[i-1][j]
    • 选i:j>coins[i]dp[i][j-coins[i]]存在,dp[i][j] = dp[i][j-coins[i]]+1
    • 二者取最小值
  • 初始化:
    • j = 0时 表示从前i个元=硬币中选出总和为0的硬币,个数为0,初始化可以省略
    • i = 0时 表示不选任何硬币使总和达到j,不存在。因为状态转移方程中取最小值,索引要初始化为无穷大,而状态转移方程中要进行加法操作,MAX_VALUE会溢出,因此要初始化成0X3f3f3f3f,足够大,不至于溢出
  • 返回值:不存在的情况都初始化为0x3f3f3f3f,因此返回时要判断,如果大于这个数,说明不存在,返回-1;否则返回dp[n][amout]

代码实现

java 复制代码
public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] dp = new int[n+1][amount+1];

        for(int j = 1;j<=amount;j++)
            dp[0][j] = 0x3f3f3f3f;


        for(int i  = 1;i<=n;i++){
            for(int j = 1;j<=amount;j++){
                dp[i][j] = dp[i-1][j];
                if(j>=coins[i-1])
                    dp[i][j] = Math.min(dp[i][j],dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[n][amount]>=0x3f3f3f3f?-1:dp[n][amount];
    }
java 复制代码
public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[] dp = new int[amount+1];

        for(int j = 1;j<=amount;j++)
            dp[j] = 0x3f3f3f3f;


        for(int i  = 1;i<=n;i++){
            for(int j = coins[i-1];j<=amount;j++){
                dp[j] = Math.min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount]>=0x3f3f3f3f?-1:dp[amount];
    }

3. 零钱兑换II(LC 518)

零钱兑换II

题目描述

解题思路

  • 状态表示:dp[i][j]表示在前i个硬币中选择总和为j时的硬币总和数
  • 状态转移方程:
    • 不选i:dp[i][j] = dp[i-1][j]
    • 选i:j >= coins[i],dp[i][j] = dp[i][j-coins[i]]
    • 二者相加
  • 初始化:
    • j = 0时,表示前i个硬币中总和为0的选法,就是都不选,有一种选法,初始化为1
    • i = 0时,表示不选任何硬币总和为j,不存在,因此为0,省略初始化

代码实现

java 复制代码
public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n+1][amount+1];
        for(int i = 0;i<=n;i++)
            dp[i][0] = 1;

        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=amount;j++){
                dp[i][j] = dp[i-1][j];
                if(j>=coins[i-1])
                    dp[i][j]+=dp[i][j-coins[i-1]];
            }
        }
        return dp[n][amount];
    }
  • 空间优化
java 复制代码
public int change(int amount, int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount+1];
        dp[0] = 1;

        for(int i = 1;i<=n;i++){
            for(int j = coins[i-1];j<=amount;j++){
                dp[j]+=dp[j-coins[i-1]];
            }
        }
        return dp[amount];
    }

4. 完全平方数(LC 279)

完全平方数

题目描述

解题思路

本质上是完全背包问题

  • 状态表示:dp[i][j]表示 从前i个完全平方数中选择总和为j的最少数量
  • 状态转移方程:
    • 不选i: dp[i][j] = dp[i-1][j]
    • 选i:j>=ii时,dp[i][j] = dp[i][j-ii]+1;
    • 二者取最小值
  • 初始化:
    • i = 0时,表示不选任何数,总和为j,不存在。因为要取最小值,还涉及到加法,为了防止溢出,初始化为0x3f3f3f3f
  • 返回值:一定存在结果,不需要特殊判断

代码实现

java 复制代码
public int numSquares(int v) {
        int n = (int)Math.sqrt(v);

        int[][] dp = new int[n+1][v+1];
        for(int j = 1;j<=v;j++)
            dp[0][j] = 0x3f3f3f3f;

        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=v;j++){
                dp[i][j] = dp[i-1][j];
                if(j>=i*i)
                    dp[i][j] = Math.min(dp[i][j],dp[i][j-i*i]+1);
            }
        }
        return dp[n][v];
    }
  • 空间优化
java 复制代码
public int numSquares(int v) {
        int n = (int)Math.sqrt(v);

        int[] dp = new int[v+1];
        for(int j = 1;j<=v;j++)
            dp[j] = 0x3f3f3f3f;

        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=v;j++){
                dp[j] = dp[j];
                if(j>=i*i)
                    dp[j] = Math.min(dp[j],dp[j-i*i]+1);
            }
        }
        return dp[v];
    }

二位费用的背包问题

二位费用的背包问题有两个限制条件,也分为01背包和完全背包

5. 一和零(LC 474)

一和零

题目描述

解题思路

每个元素只涉及选或不选的问题,因此这是01背包

  • 状态表示:dp[i][j][k]表示从前i个元素中挑选,字符0不超过j个,字符1不超过k个的所有选法中,最大长度
  • 状态转移方程:
    • 不选i:dp[i][j][k] = dp[i-1][i][k];
    • 选i: j>a 且 k > b的情况下 dp[i][j][k] = dp[i-1][j-a][k-b]+1
  • 初始化:i = 0 时,表示不选任何元素,0的个数不超过j,1的个数不超过k的最大长度,就是0,可以省略初始化

代码实现

java 复制代码
public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][][] dp = new int[len+1][m+1][n+1];

        for(int i = 1;i<=len;i++){
            for(int j = 0;j<=m;j++){
                for(int k = 0;k<=n;k++){
                    dp[i][j][k] = dp[i-1][j][k];
                    int[] tmp = count(strs[i-1]);
                    if(j>=tmp[0] && k>=tmp[1])
                        dp[i][j][k] = Math.max(dp[i][j][k],dp[i-1][j-tmp[0]][k-tmp[1]]+1);
                }
            }
        }
        return dp[len][m][n];
    }
    int[] count (String str){
        int zero = 0;
        int one = 0;
        char[] ss = str.toCharArray();
        for(char ch :ss){
            if(ch=='0')
                zero++;
            else
                one++;
        }
        return new int[]{zero,one};
    }
  • 空间优化
java 复制代码
public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][] dp = new int[m+1][n+1];

        for(int i = 1;i<=len;i++){
            int[] tmp = count(strs[i-1]);
            for(int j = m;j>=tmp[0];j--){
                for(int k =n;k>=tmp[1];k--){
                    dp[j][k] = Math.max(dp[j][k],dp[j-tmp[0]][k-tmp[1]]+1);
                }
            }
        }
        return dp[m][n];
    }
    int[] count (String str){
        int zero = 0;
        int one = 0;
        char[] ss = str.toCharArray();
        for(char ch :ss){
            if(ch=='0')
                zero++;
            else
                one++;
        }
        return new int[]{zero,one};
    }

6. 盈利计划(LC 879)

盈利计划

题目描述

翻译:有一些工作,从中挑选一部分形成子级,每一项工作都有对应的profit和group,要求这个自己的总共人数<=n,且总利润>=m

解题思路

每个工作只需要考虑选或不选,因此这是01背包问题

  • 状态表示:dp[i][j][k]表示前i个计划中挑选,总人数不超过j,总利润至少为k,一共有多少种选法
  • 状态转移方程:
    • 不选i:dp[i][j][k] = dp[i-1][j][k]
    • 选i:j>=g[i]dp[i][j][k] = dp[i-1][j-g[i]][k-p[i]]
      • k-p[i]可以小于0,也就是说,k<=p[i]的情况是成立的 ,但是在数组中,为了防止越界,不能访问负数下标,只能访问0下标。dp[i-1][j-g[i]][max(0,k-p[i])]
    • 以上两种情况相加
  • 初始化:当i=0时,表示不选任何任务,此时k=0,dp[0][j][0]表示不选任何任务,人数不超过j,盈利为0,有一种选法,要初始化为1

代码实现

java 复制代码
public int profitableSchemes(int n, int m, int[] group, int[] profit) {
        int mod =(int) 1e9+7;
        int len = group.length;
        int[][][] dp = new int[len+1][n+1][m+1];
        for(int j = 0;j<=n;j++)
            dp[0][j][0] = 1;

        for(int i = 1;i<=len;i++){
            for(int j = 0 ;j<=n;j++){
                for(int k = 0;k<=m;k++){
                    dp[i][j][k] = dp[i-1][j][k];
                    if(j>=group[i-1])
                        dp[i][j][k] += (dp[i-1][j-group[i-1]][Math.max(0,k-profit[i-1])]);
                    dp[i][j][k]%=mod;
                }
            }
        }
        return dp[len][n][m]%mod;
    }
  • 空间优化
java 复制代码
public int profitableSchemes(int n, int m, int[] group, int[] profit) {
        int mod =(int) 1e9+7;
        int len = group.length;
        int[][] dp = new int[n+1][m+1];
        for(int j = 0;j<=n;j++)
            dp[j][0] = 1;

        for(int i = 1;i<=len;i++){
            for(int j = n ;j>=group[i-1];j--){
                for(int k = m;k>=0;k--){
                        dp[j][k] += dp[j-group[i-1]][Math.max(0,k-profit[i-1])];
                    dp[j][k]%=mod;
                }
            }
        }
        return dp[n][m]%mod;
    }

似包非包

7. 组合总和(LC 377)

组合总和

题目描述

解题思路

背包问题解决的问题都是有限制条件下的组合问题,也就是说不需要考虑顺序,而这个题要考虑顺序,严格意义上背包问题不能解决。

  • 状态表示: dp[i]表示凑成总和为i有多少种排列数
  • 状态转移方程:遍历数组,dp[i] += dp[i-nums[j]]
  • 初始化:dp[0]=1,表示空集也算一种排列

代码实现

java 复制代码
public int combinationSum4(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[target+1];
        dp[0] = 1;

        for(int i = 1;i<=target;i++){
            for(int x:nums){
                if(i>=x){
                    dp[i]+=dp[i-x];
                }
            }
        }
        return dp[target];
    }

卡特兰数

8. 不同的二叉搜索树(LC 96)

不同的二叉搜索树

题目描述

解题思路

  • 状态表示:dp[i]表示节点个数为i时,二叉搜索树的数量
  • 状态转移方程:对于节点个数为i时,从1开始重新遍历,j为根节点,那么左子树的节点个数是j-1;右子树的节点个数是i-j。分部相乘,dp[i] += dp[j-1]*[i-j]
  • 初始化:空树可以认为是二叉搜索树 dp[0] = 1;

代码实现

java 复制代码
public int numTrees(int n) {
        int[] dp = new int[n+1];
        dp[0] = 1;
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=i;j++){
                dp[i] +=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
相关推荐
故事和你911 小时前
洛谷-【动态规划1】动态规划的引入2
开发语言·数据结构·c++·算法·动态规划·图论
LabVIEW开发2 小时前
LabVIEW实现FDTD 电磁仿真
算法·labview·labview知识·labview功能·labview程序
Together_CZ2 小时前
DTSemNet :Vanilla Gradient Descent for Oblique Decision Trees——用于倾斜决策树的普通梯度下降
算法·决策树·机器学习·vanilla·gradient·dtsemnet·用于倾斜决策树的普通梯度
一条大祥脚2 小时前
ABC459 贪心构造|树形DP|组合数学|贪心|单调栈|势能|前缀和
算法·深度优先
灰灰勇闯IT3 小时前
DeepEP:MoE 推理的 AllToAll 通信瓶颈怎么解
算法·cann
一行代码一行诗++3 小时前
goto语句
java·开发语言·算法
汉克老师3 小时前
GESP5级C++考试语法知识(十七、二分算法提高篇(二))
c++·算法·二分算法·gesp5级·gesp五级·二分算法易错点
叶小鸡3 小时前
小鸡玩算法-力扣HOT100-动态规划(下)
算法·leetcode·动态规划
信奥胡老师4 小时前
B3968 [GESP202403 五级] 成绩排序
数据结构·算法