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

【算法训练营Day27】动态规划part3

文章目录

  • 完全背包理论基础
  • 完全背包典例
  • 零钱兑换II
  • 组合总和 Ⅳ
  • 爬楼梯(进阶版)
  • 零钱兑换
  • 完全平方数
  • 单词拆分
  • 背包问题总结

完全背包理论基础

完全背包与01背包的唯一区别就在于完全背包的每件物品有无数个,可以放入背包多次。

我们从dp四部曲来分析一下完全背包问题:

  • dp数组及其下标:dp[i][j]表示物品0 ~ i可多次放入到容量为j的背包中最大价值是多少?
  • 递推关系:我们从01背包的递推对比来看:
    • 01背包的递推关系:关键点在于物品只能用一次,所以如果要放物品,那么肯定是没有该物品,然后把该物品的位置空出来再进行推导。所以dp[i][j]是两种情况取较大值:
      • 放物品i:dp[i - 1][j - weight[i]] + value[i]
      • 不放物品i:dp[i - 1][j]
    • 完全背包的递推关系:关键点在于物品可以使用多次,所以如果要放物品,此时是可以有该物品的,只需要把物品的位置空出来即可。所以dp[i][j]是两种情况取较大值:
      • 放物品i:dp[i][j - weight[i]] + value[i]
      • 不放物品i:dp[i - 1][j]
  • 初始化:根据推导公式以及dp含义,我们可以将容量为0的那一列先初始化为0,注意到递推式中dp[i][j]依赖左边以及上边,所以我们一定要初始化第一行,也就是只放物体0的情况。因为可以多次放物品0,所以dp[0][j] = dp[0][j - weight[0]] + value[0]
  • 遍历顺序:在01背包中是从后往前,这是因为一个物品只能取一次,而在完全背包中是从前往后,因为每个物品可以取多次。

完全背包典例

题目链接:52. 携带研究材料

import java.util.*;public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int n = in.nextInt();int bag = in.nextInt();int[] weight = new int[n];int[] values = new int[n];for(int i = 0;i < n;i++) {weight[i] = in.nextInt();values[i] = in.nextInt();}int[][] dp = new int[n][bag + 1];//初始化for(int j = 1;j <= bag;j++) if(j - weight[0] >= 0) dp[0][j] = dp[0][j - weight[0]] + values[0];//遍历for(int i = 1;i < n;i++) {for(int j = 1;j <= bag;j++) {if(j - weight[i] >= 0) dp[i][j] = Math.max(dp[i][j - weight[i]] + values[i],dp[i - 1][j]);else dp[i][j] = dp[i - 1][j];}}System.out.println(dp[n - 1][bag]);}
}

零钱兑换II

题目链接:518. 零钱兑换 II

解题逻辑:

这个题就类似于我们在01背包中做过的,把背包装满有多少种情况。所以他的递推式是相加,而不是取较大值。

dp的四部曲分析和上面的典例基本一样,就不在此赘述。

class Solution {public int change(int amount, int[] coins) {int n = coins.length;int[][] dp = new int[n][amount + 1];//初始化for(int i = 0;i < n;i++) dp[i][0] = 1;for(int j = 1;j <= amount;j++) if(j - coins[0] >= 0) dp[0][j] = dp[0][j - coins[0]];//遍历for(int i = 1;i < n;i++) {for(int j = 1;j <= amount;j++) {if(j - coins[i] >= 0) dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];else dp[i][j] = dp[i - 1][j];}}return dp[n - 1][amount];}
}

一维dp数组:

class Solution {public int change(int amount, int[] coins) {int[] dp = new int[amount + 1];//初始化dp[0] = 1;//遍历for(int i = 0;i < coins.length;i++) {for(int j = 1;j <= amount;j++) {if(j - coins[i] >= 0) dp[j] = dp[j] + dp[j - coins[i]];}}return dp[amount];}
}

组合总和 Ⅳ

题目链接:377. 组合总和 Ⅳ

这一题和上面的题很相似,也是求把背包塞满有多少种情况。区别在于上一题是求组合数(也就是不考虑顺序),而本题是求排列数(也就是要考虑顺序),两者的处理区别在于:

  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。

为什么?

  • 先遍历物品,再遍历背包。相当于固定了物品之后,对全容量进行递推,每次外循环新增一个后面的元素,所以dp数组里存的是组合的情况。
  • 先遍历背包,再遍历物品。相当于固定了容量,对物品进行递推,当前容量可以由任意物品组成,进入下一轮容量固定之后,其依赖上一轮的结果,并且此轮也可以由任意物品组成,所以dp数组里面存的是排列的情况。

解题代码如下:

class Solution {public int combinationSum4(int[] nums, int target) {int n = nums.length;int[] dp = new int[target + 1];//递推式:一维dp :dp[j] = dp[j] + dp[j - nums[i]]//初始化dp[0] = 1;//遍历for(int j = 1;j <= target;j++) {for(int i = 0;i < n;i++) {if(j - nums[i] >= 0) dp[j] = dp[j] + dp[j - nums[i]];}}return dp[target];}
}

爬楼梯(进阶版)

题目链接:57. 爬楼梯

解题思路同上。

解题代码:

import java.util.*;public class Main{public static void main(String[] args) {Scanner in = new Scanner(System.in);int bag = in.nextInt();int m = in.nextInt();int[] dp = new int[bag + 1];//递推 dp[j] = dp[j] + dp[j - value[i]]dp[0] = 1;for(int j = 1;j <= bag;j++) {for(int i = 0;i < m;i++) {if(j >= i + 1) {dp[j] = dp[j] + dp[j - (i + 1)];}}}System.out.println(dp[bag]);}
}

零钱兑换

题目链接:322. 零钱兑换

解题逻辑:

从dp四部曲分析:

  • dp数组及下标含义:dp[j]表示装满容量为j的背包最少的硬币个数
  • 递推式:dp[i][j] = min(dp[i - 1][j],dp[i][j - values[i]] + 1),简化为一维dp,则dp[j] = min(dp[j],dp[j - values[i]] + 1)
  • 初始化:dp[0] = 0,其余全部初始化为Integer.MAX_VALUE - 1,减1是因为防止递推的时候 + 1导致变为负数从而取最小的时候误取。
  • 遍历顺序:先遍历物品,再遍历背包,使用物品的组合可以解决问题。

代码如下:

class Solution {public int coinChange(int[] coins, int amount) {int n = coins.length;int[] dp = new int[amount + 1];//初始化for(int i = 1;i <= amount;i++) dp[i] = Integer.MAX_VALUE - 1;//遍历for(int i = 0;i < n;i++) {for(int j = coins[i];j <= amount;j++) {dp[j] = Math.min(dp[j],dp[j - coins[i]] + 1);}}return dp[amount] == Integer.MAX_VALUE - 1 || dp[amount] < 0 ? -1 : dp[amount];}
}

或者当遍历到Integer.MAX_VALUE直接跳过,也就是如果取当前元素,那么dp[j - coins[i]]需要先被取到:

class Solution {public int coinChange(int[] coins, int amount) {int n = coins.length;int[] dp = new int[amount + 1];//初始化for(int i = 1;i <= amount;i++) dp[i] = Integer.MAX_VALUE;//遍历for(int i = 0;i < n;i++) {for(int j = coins[i];j <= amount;j++) {if(dp[j - coins[i]] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j],dp[j - coins[i]] + 1);}}return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}
}

完全平方数

题目链接:279. 完全平方数

解题逻辑和上一题一样,代码如下:

class Solution {public int numSquares(int n) {int[] dp = new int[n + 1];for(int i = 1;i <= n;i++) dp[i] = Integer.MAX_VALUE;for(int i = 1;i <= 100;i++) {for(int j = i * i;j <= n;j++) {if(dp[j - i * i] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j],dp[j - i * i] + 1);}}return dp[n];}
}

单词拆分

题目链接:139. 单词拆分

解题逻辑:

从dp四部曲分析:

  • dp数组及下标含义:dp[j]表示s的前j个元素能否被拼出
  • 递推式:也是分为取和不取两种情况。dp[j] = dp[j] || (wordDict.get(i).equals(s.substring(j - wordDict.get(i).length(),j)) && dp[j - wordDict.get(i).length()])
  • 初始化:dp[0] = true
  • 遍历顺序:先遍历背包,再遍历物品,使用物品的排列可以解决问题。

代码逻辑:

class Solution {public boolean wordBreak(String s, List<String> wordDict) {int n = wordDict.size();int bag = s.length();boolean[] dp = new boolean[bag + 1];//初始化dp[0] = true;for(int j = 1;j <= bag;j++) {for(int i = 0;i < n;i++) {if(wordDict.get(i).length() <= j) {dp[j] = dp[j] || (wordDict.get(i).equals(s.substring(j - wordDict.get(i).length(),j)) && dp[j - wordDict.get(i).length()]);}}}return dp[bag];}
}

背包问题总结

关于(一维dp数组)常用的递推公式:

  • 问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
  • 问装满背包有几种方法:dp[j] += dp[j - nums[i]]
  • 问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
  • 问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j])

关于遍历顺序:

  • 01背包
    • 二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。
    • 一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历
  • 完全背包
    • 一维dp数组实现,先遍历物品还是先遍历背包需要分情况,且第二层for循环是从小到大遍历。
    • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
    • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
http://www.dtcms.com/a/431937.html

相关文章:

  • 网站后台管理系统软件开发一款app成本
  • 衡阳网站建设专家今天的特大新闻有哪些
  • 电子政务网站建设西安网站建设电话
  • 论文解读:GRAPHEVAL: A LIGHTWEIGHT GRAPH-BASED LLM FRAMEWORK FOR IDEA EVALUATION
  • 门业网站 模板it运维工程师工作内容
  • 河北省住房和城乡建设厅网站打不开学做网站论坛教程下载
  • Java 异常体系:从 Throwable 根类到自定义异常,一篇理清所有分类与逻辑
  • 仿5173网站汕尾旅游攻略app跳转网站
  • memory_profiler各个参数都是什么意思?
  • 网站开发技术简介dwsynology建设网站
  • p2p网站建设框架如何在google上免费推广
  • win7 win10 win11安装IE11浏览器
  • 深度学习池化(Pooling)的进阶应用与优化策略
  • 家庭宽带 做网站wordpress登陆后返回
  • 松岗网站的建设sasaki景观设计公司官网
  • 关于win11的Microsoft To Pdf打印机修改端口后无法再刷新显示于设备界面的问题
  • 深圳市网站建设外包公司排名某个网站做拍卖预展的好处
  • 苏州做网站公司认定苏州聚尚网络创新创意产品设计作品
  • AOI检测在半导体制造领域有哪些主要应用
  • 含山建设局网站支持快钱支付的网站
  • Win环境下包管理工具
  • 陕西西安网站建设公司哪家好微网站方案怎么写
  • 阜阳网站制作公司去哪找山东省城乡住房和城乡建设厅网站
  • wordpress 在线音乐播放器seo外包公司哪家专业
  • 如何在Windows操作系统上使用Makefile?
  • 英文网站seo 谷歌公司起名网站十大排名
  • SDK游戏盾的应用场景有哪些?
  • 安卓基础组件020-页面跳转001
  • 如何做盗版小说网站公司网站建设会计处理
  • 免费建设个人网站企业网站建设一站通系统简单