当前位置: 首页 > news >正文

Day37--动态规划--52. 携带研究材料(卡码网),518. 零钱兑换 II,377. 组合总和 Ⅳ,57. 爬楼梯(卡码网)

Day37–动态规划–52. 携带研究材料(卡码网),518. 零钱兑换 II,377. 组合总和 Ⅳ,57. 爬楼梯(卡码网)

本文全部都是 ” 完全背包 “ 问题,从零到入坑,从入坑到爬出来。

本文的精华在:《为什么是“先遍历背包,后遍历数字”?》非常详细地讲解了,为什么先遍历数字,后遍历背包——是组合数。先遍历背包,后遍历数字——是排列数。

52. 携带研究材料(卡码网)

思路:

  1. 确定dp数组含义:dp[i][j] 表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少
  2. 确定递推公式:(和01背包相似,所以只讲区别)
    • 在01背包中,每个物品只有一个,既然空出物品1,那背包中也不会再有物品1。所以空出来之后,去上一层找,即dp[i-1][j-weight[i]]
    • 而在完全背包中,每个物品可以无限取,即使空出物品1空间重量,那背包中也可能还有物品1。所以空出来之后,要从本层取,即dp[i][j-weight[i]]
    • 得出递推公式:dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
  3. 初始化:初始化第一行,从背包能放得下weight[0]这个东西开始,往后遍历。能放多少是多少。
  4. 确定遍历顺序,每个dp[i][j],都要靠左边,上边的值确定数据。所以是从上往下,从左往右。
import java.util.*;public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int n = in.nextInt();int bagSize = in.nextInt();int[] weight = new int[n];int[] value = new int[n];for (int i = 0; i < n; i++) {weight[i] = in.nextInt();value[i] = in.nextInt();}int[][] dp = new int[n][bagSize + 1];// 初始化第一行,从背包能放得下weight[0]这个东西开始,往后遍历for (int j = weight[0]; j <= bagSize; j++) {dp[0][j] = dp[0][j - weight[0]] + value[0];}// 动态规划for (int i = 1; i < n; i++) {for (int j = 1; j <= bagSize; j++) {// 当前背包放不下weight[i],直接等于上一层if (j < weight[i]) {dp[i][j] = dp[i - 1][j];} else {// 这里的递归公式,与01背包不同。要空出weight[i]的时候,看的是本层的dp// 区别在于,这里是dp[i][j - weight[i]],而01背包是:dp[i-1][j - weight[i]]dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);}}}System.out.println(dp[n - 1][bagSize]);}
}

518. 零钱兑换 II

思路:二维dp数组

  1. 确定dp[i][j]含义,从0-i的coins中,任选一个或多个,装满容量为j的背包(注意是装满)
  2. 确定递推公式:
    1. 注意这里求的是“最大组合数”,而不是“最大价值”,所以是加法(情况一+二)
    2. 情况一,不放coins[i],就是dp[i-1][j],情况二,放coins[i],就是dp[i][j-coins[i]]
    3. 注意这里不是[i-1]了,是[i]。不是看上层的,而是看本层的,这是完全背包和01背包的关键区别
  3. 初始化
    1. 初始化第一列:都有“不选”的选择,所以是1
    2. 初始化第一行:要能取模的才能赋值1,比如最小货币是2的话,当j==3,是不能装满的
// 确定dp[i][j]含义,从0-i的coins中,任选一个或多个,装满容量为j的背包
class Solution {public int change(int amount, int[] coins) {// amount 是 bagSizeint n = coins.length;int[][] dp = new int[n][amount + 1];// 初始化第一列for (int i = 0; i < n; i++) {// 都有“不选”的选择,所以是1dp[i][0] = 1;}// 初始化第一行for (int j = coins[0]; j <= amount; j++) {// 特别注意,要能取模的才能赋值1,比如最小货币是2的话,当j==3,是不能装满的if (j % coins[0] == 0) {dp[0][j] = 1;}}// 动态规划for (int i = 1; i < n; i++) {for (int j = 1; j <= amount; j++) {// 当前硬币大于背包容量,直接等于上一层if (j < coins[i]) {dp[i][j] = dp[i - 1][j];} else {// 注意这里求的是“最大组合数”,而不是“最大价值”,所以是加法(情况一+二)// 情况一,不放coins[i],就是dp[i-1][j]// 情况二,放coins[i],就是dp[i][j-coins[i]]// 注意这里不是[i-1]了,是[i]。不是看上层的,而是看本层的,这是完全背包和01背包的关键区别dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];}}}return dp[n - 1][amount];}
}

思路:一维dp数组

  1. dp[i][j]含义不变
  2. 递推公式:
    1. 本题 二维dp 递推公式: dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]](正上方,左方)
    2. 压缩成一维:dp[j] = dp[j] + dp[j - coins[i]]
  3. 初始化:dp[0] = 1
  4. 遍历顺序
    1. 在01背包的时候,遍历j是要倒序遍历的,因为dp[j]要依靠“正上方”和“左上方”的数据。但是完全背包,它依赖的是:“正上方”和“左方”的数据。
    2. “左方”的数据由何而来?得先遍历才有,所以是for(j)是顺序遍历

附:01背包时候的二维公式和一维公式

二维: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - coins[i]](正上方+左上方)

一维:dp[j] = dp[j] + dp[j - nums[i]];

因为要依赖左上方的数据,也就是上一层的旧数据,所以只能倒序遍历。遍历右的时候能取到左的数据

// 确定dp[i][j]含义,从0-i的coins中,任选一个或多个,装满容量为j的背包
class Solution {public int change(int amount, int[] coins) {// amount 是 bagSizeint n = coins.length;int[] dp = new int[amount + 1];// 初始化dp[0] = 1;// 动态规划for (int i = 0; i < n; i++) {for (int j = coins[i]; j <= amount; j++) {dp[j] = dp[j] + dp[j - coins[i]];}}return dp[amount];}
}

377. 组合总和 Ⅳ

写在前面:这个题目很有迷惑性,说是“组合”总和,但是这个“组合”又是可以有不同序列的——其实就是“排列”数,而不是求“组合”数。一定要弄清楚这个再继续。

思路:一维dp数组

  1. dp[j]的定义:从[0-i](可重复)取任意个数,填满背包j的组合(可排列!)有多少种?
  2. 确定递归公式:dp[j] = dp[j] + dp[j - nums[i]];(和上题一样,不再详细分析)
  3. 初始化:dp[0] = 1;
  4. 遍历顺序:先遍历背包,再遍历数字!!!先遍历背包,再遍历数字!!!先遍历背包,再遍历数字!!!
// 一维dp数组
// dp[j]的定义:从[0-i](可重复)取任意个数,填满背包j的组合(可排列!)有多少种?
class Solution {public int combinationSum4(int[] nums, int target) {int n = nums.length;// bagSize 是 targetint[] dp = new int[target + 1];// 初始化dp[0] = 1;// 动态规划for (int j = 0; j <= target; j++) {for (int i = 0; i < n; i++) {if (j >= nums[i]) {dp[j] = dp[j] + dp[j - nums[i]];}}// // 遍历dp检查// for(int k=0;k<=target;k++){//     System.out.print(dp[k]+" ");// }// System.out.println(" ");}return dp[target];}
}

为什么是“先遍历背包,后遍历数字”?

先说结论:

先遍历数字,后遍历背包——是组合数。

先遍历背包,后遍历数字——是排列数。

为什么会这样呢?

公式 dp[j] += dp[j - nums[i]] 的核心逻辑是:

要计算 “填满容量 j 的方式数”,可以先放入一个 nums [i],再累加 “填满剩余容量 j-nums [i] 的方式数”

  • 当先遍历数字时,“最后一个元素” 只能是当前数字(按固定顺序),所以不会产生新顺序。
  • 当先遍历背包时,“最后一个元素” 可以是任何数字(在同一容量下尝试所有数字),所以会产生不同顺序的排列。

公式本身不关心顺序,只关心 “是否加入当前数字”,而遍历顺序决定了 “加入数字的时机和范围”,最终导致结果差异。

到这里,基本上讲清楚了。可以再把518. 零钱兑换 II按照“先遍历背包,后遍历数字”的方式写一遍,答案肯定错误。

57. 爬楼梯(卡码网)

《代码随想录》:

这其实是一个完全背包问题。

1阶,2阶,… m阶就是物品,楼顶就是背包。

每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。

问跳到楼顶有几种方法其实就是问装满背包有几种方法。

思路:

  1. 确定dp含义:dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法
  2. 确定递推公式:dp[j] += dp[j - i];
  3. 初始化:dp[0] = 1;
  4. 完全背包问题:先背包,后数字(因为要先固定背包容量,再取数字,才有不同的顺序)

要是能识别出来是完全背包问题,然后又刚好做完上一题的话,这道题其实可以直接复制过去AC。

import java.util.*;public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int n = in.nextInt();int m = in.nextInt();// bagSize 是 n// coins[] 是 m, [1,2,3...m]// dpint[] dp = new int[n + 1];// 初始化dp[0] = 1;// 先背包,后数字for (int j = 0; j <= n; j++) {for (int i = 1; i <= m; i++) {if (j >= i) {dp[j] += dp[j - i];}}}System.out.println(dp[n]);}
}
http://www.dtcms.com/a/323510.html

相关文章:

  • Web前端之 ECMAScript6
  • 【ros_humble】3.人脸检测python(服务通讯和参数通讯介绍)
  • 关于Linux软件编程1
  • leetcode 128. 最长连续序列 - java
  • 【网络与爬虫 51】Scrapy-Cluster分布式爬虫集群:企业级大规模数据采集解决方案
  • 卷积神经网络学习
  • 新手小白使用jQuery在实际开发中常用到的经验
  • 讯飞晓医-讯飞医疗推出的个人AI健康助手
  • 初学python的我开始Leetcode题15-2
  • Web自动化技术选择
  • SpringBoot日志关系
  • 【能碳建设1】用AI+开源打造物联网+能碳管理+交易SaaS系统的最短路径实施指南
  • C#:dnSpy
  • 【密码学】7. 数字签名
  • 聚众识别误报率↓78%:陌讯动态密度感知算法实战解析
  • CentOS7编译安装GCC
  • Python基础教程(六)条件判断:引爆思维Python条件判断的九层境界
  • Java Stream流详解:用法与常用API实战
  • Kotlin 协程线程切换机制详解
  • 规划是自动驾驶的“头脑”
  • 灰度测试(灰度发布、金丝雀发布)
  • 推荐系统学习笔记(十一)预估分数融合
  • leetcode-C语言-3479.水果成篮 III
  • C++ vector类
  • 3.2Vue Router路由导航
  • 【熵增与熵减:从混乱到有序的奥秘】
  • 词向量可视化:用TensorBoard或PCA探索词向量空间
  • 【JavaEE】(11) 前端基础三件套
  • 大数据与财务管理:未来就业的黄金赛道
  • java9学习笔记-part2