文章目录

  • 四、完全背包
      • 01背包的核心代码
      • 完全背包的核心代码
    • 12、零钱兑换 II
    • 13、组合总和 Ⅳ
    • 14、 爬楼梯(进阶版)
    • 15.零钱兑换
    • 16.完全平方数
    • 17.单词拆分

四、完全背包

完全背包:每一个物品可以选无限次

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

01背包的核心代码

for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}

我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。

而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

完全背包的核心代码

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}
  • 在一维数组中,01背包问题必须先遍历物品,后遍历背包。完全背包两个for循环嵌套顺序是无所谓的
  • 在一维数组中,01背包问题遍历背包时必须倒序遍历。完全背包正序遍历
  • 倒序遍历,必须先遍历物品,再遍历背包。正序遍历,则for循环嵌套顺序无所谓

Code

//先遍历物品,再遍历背包
private static void testCompletePack(){int[] weight = {1, 3, 4};int[] value = {15, 20, 30};int bagWeight = 4;int[] dp = new int[bagWeight + 1];for (int i = 0; i < weight.length; i++){ // 遍历物品for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);}}for (int maxValue : dp){System.out.println(maxValue + "   ");}
}//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){int[] weight = {1, 3, 4};int[] value = {15, 20, 30};int bagWeight = 4;int[] dp = new int[bagWeight + 1];for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量for (int j = 0; j < weight.length; j++){ // 遍历物品if (i - weight[j] >= 0){dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);}}}for (int maxValue : dp){System.out.println(maxValue + "   ");}
}

12、零钱兑换 II

力扣题目链接

  • 组合不强调元素之间的顺序,排列强调元素之间的顺序

思路

动规五部曲

  • 确定dp数组以及下标的含义

dp[j]:凑成总金额j的货币组合数为dp[j]

  • 确定递推公式

dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。

所以递推公式:dp[j] += dp[j - coins[i]];

  • dp数组初始化

首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础,下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]

  • 确定遍历顺序

求组合数,先遍历物品,再遍历背包

for (int i = 0; i < coins.size(); i++) { // 遍历物品for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量dp[j] += dp[j - coins[i]];}
}

求排列数,先遍历背包,再遍历物品

for (int j = 0; j <= amount; j++) { // 遍历背包容量for (int i = 0; i < coins.size(); i++) { // 遍历物品if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];}
}
  • 举例推导dp数组

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

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

Code

class Solution {public int change(int amount, int[] coins) {//递推表达式int[] dp = new int[amount + 1];//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装dp[0] = 1;for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) {dp[j] += dp[j - coins[i]];}}return dp[amount];}
}

13、组合总和 Ⅳ

力扣题目链接

本题与零钱兑换差别就在于物品和背包的遍历顺序

动规五部曲

  • 确定dp数组以及下标的含义

dp[i]: 凑成目标正整数为i的排列个数为dp[i]

  • 确定递推公式

dp[i] += dp[i - nums[j]];

  • dp数组如何初始化

dp[0] = 1

  • 确定遍历顺序

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

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

遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历

  • 举例推导dp数组

Code

class Solution {public int combinationSum4(int[] nums, int target) {int[] dp = new int[target + 1];dp[0] = 1;for (int i = 0; i <= target; i++) {for (int j = 0; j < nums.length; j++) {if (i >= nums[j]) {dp[i] += dp[i - nums[j]];}}}return dp[target];}
}

14、 爬楼梯(进阶版)

力扣题目链接

题目改为:一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?

思路

动规五部曲

  • 确定dp数组以及下标的含义

dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。

  • 确定递推公式

本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]

那么递推公式为:dp[i] += dp[i - j]

  • dp数组初始化

既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。

下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果

  • 确定遍历顺序

这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!

所以需将背包放在外循环,而物品放在内循环。

  • 举例并推导dp数组

总结

本题看起来是一道简单题目,稍稍进阶一下其实就是一个完全背包!

如果我来面试的话,我就会先给候选人出一个 本题原题,看其表现,如果顺利写出来,进而在要求每次可以爬[1 - m]个台阶应该怎么写。

顺便再考察一下两个for循环的嵌套顺序,为什么target放外面,nums放里面。

这就能考察对背包问题本质的掌握程度,候选人是不是刷题背公式,一眼就看出来了。

这么一连套下来,如果候选人都能答出来,相信任何一位面试官都是非常满意的。

本题代码不长,题目也很普通,但稍稍一进阶就可以考察完全背包,而且题目进阶的内容在leetcode上并没有原题,一定程度上就可以排除掉刷题党了,简直是面试题目的绝佳选择!

Code

爬楼梯

class Solution {public int climbStairs(int n) {int[] dp = new int[n + 1];int[] weight = {1,2};dp[0] = 1;for (int i = 0; i <= n; i++) {for (int j = 0; j < weight.length; j++) {if (i >= weight[j]) dp[i] += dp[i - weight[j]];}}return dp[n];}
}

爬楼梯(进阶版)

class Solution {public int combinationSum4(int[] nums, int target) {int[] dp = new int[target + 1];dp[0] = 1;for (int i = 0; i <= target; i++) {for (int j = 0; j < nums.length; j++) {if (i >= nums[j]) {dp[i] += dp[i - nums[j]];}}}return dp[target];}
}

15.零钱兑换

力扣题目链接

思路

动规五部曲

  • 确定dp数组以及下标的含义

dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

  • 确定递推公式

凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j](考虑coins[i])

所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。

递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

  • dp数组如何初始化

dp[0] = 0;

其他下标对应的数值呢?

考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

所以下标非0的元素都是应该是最大值。

  • 确定遍历顺序

所以本题并不强调集合是组合还是排列。

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

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

遍历顺序不做要求,但我们采用物品放外层,背包放内层。

  • 举例推导dp数组

dp[amount]为最终结果。

Code

class Solution {public int coinChange(int[] coins, int amount) {int max = Integer.MAX_VALUE;int[] dp = new int[amount + 1];Arrays.fill(dp,max);dp[0] = 0;for(int i : coins){for(int j = i; j <= amount; j++){if(dp[j - i] != max){dp[j] = Math.min(dp[j], dp[j - i] + 1);}}}return dp[amount] == max ? -1 : dp[amount];}
}

16.完全平方数

力扣题目链接

思路

动规五部曲

  • 确定dp数组以及下标的含义

dp[j]:和为j的完全平方数的最少数量为dp[j]

  • 确定递推公式

dp[j] = min(dp[j - i * i] + 1, dp[j]);

  • dp数组初始化

dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0

从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[j]一定要初始为最大值,这样dp[j]在递推的时候才不会被初始值覆盖

  • 确定遍历顺序

本题外层for遍历背包,内层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的!

    for (int i = 1; i * i <= n; i++) {// 遍历背包for (int j = i * i; j <= n; j++) {dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}
  • 举例并推导dp数组

Code

class Solution {public int numSquares(int n) {int max = Integer.MAX_VALUE;int[] dp = new int[n + 1];Arrays.fill(dp,max);dp[0] = 0;// 遍历物品for (int i = 1; i * i <= n; i++) {// 遍历背包for (int j = i * i; j <= n; j++) {dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}return dp[n];}
}

17.单词拆分

力扣题目链接

思路

动规五部曲

  • 确定dp数组以及下标的含义

dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。

  • 确定递推公式

如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。

所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。

  • dp数组如何初始化

dp[0]一定要为true,下标非0的dp[i]初始化为false

  • 确定遍历顺序

本题求排列数,排列数就是外层for遍历背包,内层for循环遍历物品。

  • 举例推导dp数组

dp[s.size()]就是最终结果。

Code

class Solution {public boolean wordBreak(String s, List<String> wordDict) {boolean[] dp = new boolean[s.length() + 1];dp[0] = true;for (int i = 1; i <= s.length(); i++) {for (String word : wordDict) {int len = word.length();if (i >= len && dp[i - len] && word.equals(s.substring(i - len, i))) {dp[i] = true;break;}}}return dp[s.length()];}
}

Day41——Dp专题相关推荐

  1. DP专题考试总结(4)

    最近努力学(tui)习(fei)了DP专题,然后考试又挂了,然后就没有然后了. 对此,我只想说-- 吾每念,常痛于骨髓,顾计不知所出耳! 广场铺砖问题 期望得分:40 实际得分:10 Descript ...

  2. DP专题考试总结(2)

    最近努力学(tui)习(fei)了DP专题,然后就被烤焦了,然后就没有然后了. 对此,我只想说-- DP made me Boom-Sha-Ka-La-Ka 加分二叉树 期望得分:0 实际得分:0 D ...

  3. UVa在线比赛单题汇总-----DP专题

    动态规划基础 例题 LA 3882 UVa 3882 - And Then There Was One 递推------------无力orz UVa 10635 10635 - Prince and ...

  4. DP专题训练之HDU 1087 Super Jumping!

    Description Nowadays, a kind of chess game called "Super Jumping! Jumping! Jumping!" is ve ...

  5. codeforces(牛客网dp专题,排序)

    链接:https://ac.nowcoder.com/acm/problem/21314 来源:牛客网 牛牛正在打一场CF 比赛时间为T分钟,有N道题,可以在比赛时间内的任意时间提交代码 第i道题的分 ...

  6. 牛客网dp专题 数位dp

    文章目录 数位dp 例题: NC116652 uva11038 How many 0's NC15035 送分了QAQ NC20669 诡异数字 NC20665 7的意志 NC17385 Beauti ...

  7. HDOJ树形DP专题之Centroid

    题目链接 这题跟Balance Act那题差不多,求图的质点.我直接将那题改了一下提交,结果PE了一次,又WA了一次,最后发现是单case,多case的提交为什么WA呢? View Code 1 #i ...

  8. 多重背包(dp专题)

    题目大意:输入n,代表有n种数,接下来n个数代表n种数,再接下来n个数代表每种数有多少个,在输入K,代表用这些数要加成的和 问你是否能加为K,能输出yes,不能输出no 这是一个典型的多重背包问题,可 ...

  9. 简单的dp(dp专题)

    题目链接:https://vjudge.net/contest/216347#problem/C Alice gets two sequences A and B. A easy problem co ...

最新文章

  1. python批量重命名指定目录下所有文件的后缀名
  2. 『互联网架构』软件架构-spring源码之spring结构概述
  3. STM32堆和栈(Heap Stack)及SRAM存储使用
  4. 原生js实现preAll和nextAll方法
  5. django配置templates、static、media和连接mysql数据库
  6. html设置table表格的弧度,用CSS3和table标签实现一个圆形轨迹的动画的示例代码
  7. 【three.js】库
  8. nodejs nodemailer
  9. 初学者学习C++的50条忠告
  10. 读取 Excel 之 Epplus
  11. 程序员是如何一步一步被诈骗的?《原力计划-打卡挑战》第二周周榜揭晓
  12. docker 网桥冲突了解决
  13. 2017-2018-1 20155315 《信息安全系统设计基础》第11周学习总结
  14. 19范数理论及其应用
  15. php pdo连接oracle乱码,PHP编程:php pdo oracle中文乱码的快速解决方法
  16. 使用虹软SDK实现离线人脸识别(局域网)
  17. 哥德巴赫猜想python
  18. 【计算机组成原理】中央处理器总结——基本知识要点汇总
  19. 云原生安全之容器级网站防篡改
  20. 安卓内存监控工具,2021年Android面试心得,系列教学

热门文章

  1. 加权平均资本成本(Weighted Average Cost of Capital,WACC)
  2. 多台计算机使用一个硬盘,怎么实现多台电脑共用一块硬盘
  3. python3+selenium框架设计04-封装测试基类
  4. 我为什么要立刻放弃React而使用Vue?
  5. 给body设置背景图片,整个图片完整的充满屏幕
  6. start with connect by prior的使用方法
  7. 【刘文彬】【源码解读】EOS测试插件:txn_test_gen_plugin.cpp
  8. vertica MySQL_MySQL数据库之Vertica数据库的用法
  9. mysql 跨库插入_sql 跨库和域插入数据库
  10. 2020找工作更难了?做好这4方面,找到高薪好工作