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

leetcode算法刷题的第三十二天

1.完全背包问题---二维dp数组

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。

小明的行李箱所能承担的总重量是有限的,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料可以选择无数次,并且可以重复选择。

输入描述

第一行包含两个整数,n,v,分别表示研究材料的种类和行李所能承担的总重量 

接下来包含 n 行,每行两个整数 wi 和 vi,代表第 i 种研究材料的重量和价值

输出描述

输出一个整数,表示最大价值。

输入示例
4 5
1 2
2 4
3 4
4 5
输出示例
10
提示信息

第一种材料选择五次,可以达到最大值。

数据范围:

1 <= n <= 10000;
1 <= v <= 10000;
1 <= wi, vi <= 10^9.

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){int n,bagWeight;int w,v;cin>>n>>bagWeight;vector<int> weight(n);vector<int> value(n);for(int i=0;i<n;i++){cin>>weight[i]>>value[i];}vector<vector<int>> dp(n,vector<int>(bagWeight+1,0));//初始化for(int j=weight[0];j<=bagWeight;j++){dp[0][j]=dp[0][j-weight[0]]+value[0];}for(int i=1;i<n;i++){//遍历物品for(int j=0;j<=bagWeight;j++){//遍历背包容量if(j<weight[i]) dp[i][j]=dp[i-1][j];else dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i]);}}cout<<dp[n-1][bagWeight]<<endl;return 0;
}

思路总结:

第一,确定dp数组以及下标的含义

dp[i][j] 表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少

第二,确定递推公式

递推公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])

第三,dp数组如何初始化

关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱

首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 可以看出有一个方向 i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

dp[0][j],即:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

j >= weight[0]时,dp[0][j] 如果能放下weight[0]的话,就一直装,每一种物品有无限个

dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?

其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由上方和左方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。

但只不过一开始就统一把dp数组统一初始为0,更方便一些。

第四,确定遍历顺序

可以先遍历物品再遍历背包

也可以先遍历背包再遍历物品

第五,举例推导dp数组

2.leetcode 518.零钱兑换II

题目链接

class Solution {
public:int change(int amount, vector<int>& coins) {int bagSize=amount;vector<vector<uint64_t>> dp(coins.size(),vector<uint64_t>(bagSize+1,0));//初始化最上面那一行for(int j=0;j<=bagSize;j++){if(j%coins[0]==0) dp[0][j]=1;}//初始化最左列for(int i=0;i<coins.size();i++){dp[i][0]=1;}//以下遍历顺序行列可以颠倒for(int i=1;i<coins.size();i++){//行,遍历物品for(int j=0;j<=bagSize;j++){//列,遍历背包if(coins[i]>j) dp[i][j]=dp[i-1][j];else dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]];}}return dp[coins.size()-1][bagSize];}
};

思路总结:

第一,确定dp数组以及下标的含义

定义二维dp数值 dp[i][j]:使用 下标为[0, i]的coins[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种组合方法。

第二,确定递推公式

本题递推公式:dp[i][j] = dp[i - 1][j] + dp[i][j - nums[i]] ,区别依然是 dp[i - 1][j - nums[i]] 和 dp[i][j - nums[i]]

第三, dp数组如何初始化

那么二维数组的最上行 和 最左列一定要初始化,这是递推公式推导的基础

这里首先要关注的就是 dp[0][0] 应该是多少?

背包空间为0,装满「物品0」 的组合数有多少呢?

应该是 0 个, 但如果 「物品0」 的 数值就是0呢? 岂不是可以有无限个0 组合 和为0!

题目描述中说了1 <= coins.length <= 300 ,所以不用考虑 物品数值为0的情况。

那么最上行dp[0][j] 如何初始化呢?

dp[0][j]的含义:用「物品0」(即coins[0]) 装满 背包容量为j的背包,有几种组合方法。

如果 j 可以整除 物品0,那么装满背包就有1种组合方法。

最左列如何初始化呢?

dp[i][0] 的含义:用物品i(即coins[i]) 装满容量为0的背包 有几种组合方法。

都有一种方法,即不装。

所以 dp[i][0] 都初始化为1

第四,确定遍历顺序

二维DP数组的完全背包的两个for循环先后顺序是无所谓的。

先遍历背包,还是先遍历物品都是可以的。

第五,打印dp数组

dp数组应该是这样的:

1 0 1 0 1 0
1 0 1 1 1 1
1 0 1 1 1 2

3.leetcode 377.组合总和IV

题目链接

class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target + 1, 0);dp[0] = 1;for (int i = 0; i <= target; i++) { // 遍历背包for (int j = 0; j < nums.size(); j++) { // 遍历物品if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {dp[i] += dp[i - nums[j]];}}}return dp[target];}
};

思路总结:这道题的答案是这样的,但是力扣里面过不了,是因为C++测试用例有两个数相加超过int的数据。

第一,确定dp数组以及下标的含义

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

第二,确定递推公式

dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导出来。

因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。

求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

本题也一样。

第三,dp数组的初始化

因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。

至于dp[0] = 1 有没有意义呢?

其实没有意义,所以我也不去强行解释它的意义了,因为题目中也说了:给定目标值是正整数! 所以dp[0] = 1是没有意义的,仅仅是为了推导递推公式。

至于非0下标的dp[i]应该初始为多少呢?

初始化为0,这样才不会影响dp[i]累加所有的dp[i - nums[j]]。

第四,确定遍历顺序

个数可以不限使用,说明这是一个完全背包。

得到的集合是排列,说明需要考虑元素之间的顺序。

本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。

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

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

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

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

第五,举例推导dp数组

求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!

如果对遍历顺序没有深度理解的话,做这种完全背包的题目会很懵逼,即使题目刷过了可能也不太清楚具体是怎么过的。

4.爬楼梯(进阶版)

题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 

每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢? 

注意:给定 n 是一个正整数。

输入描述

输入共一行,包含两个正整数,分别表示n, m

输出描述

输出一个整数,表示爬到楼顶的方法数。

输入示例
3 2
输出示例
3
提示信息

数据范围:
1 <= m < n <= 32;

当 m = 2,n = 3 时,n = 3 这表示一共有三个台阶,m = 2 代表你每次可以爬一个台阶或者两个台阶。

此时你有三种方法可以爬到楼顶。

1 阶 + 1 阶 + 1 阶段

1 阶 + 2 阶

2 阶 + 1 阶

#include<iostream>
#include<vector>
using namespace std;
int main(){int n,m;while(cin>>n>>m){vector<int> dp(n+1,0);dp[0]=1;for(int i=1;i<=n;i++){//遍历背包for(int j=1;j<=m;j++){//遍历物品if(i-j>=0) dp[i]+=dp[i-j];}}cout<<dp[n]<<endl;}return 0;
}

思路总结:

第一,确定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 步都是上三个台阶,但是这两种方法不一样!

所以需将target放在外循环,将nums放在内循环。

每一步可以走多次,这是完全背包,内循环需要从前向后遍历。

第五,举例推导dp数组

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

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

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

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

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

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


文章转载自:

http://qSsJf81a.zstry.cn
http://iRXXbaVB.zstry.cn
http://KpmtolJu.zstry.cn
http://uh615kiP.zstry.cn
http://SYatfuZ7.zstry.cn
http://Gib5ye7k.zstry.cn
http://MBcAPAO9.zstry.cn
http://FwiIPAHx.zstry.cn
http://icEbImlG.zstry.cn
http://lZ1M562M.zstry.cn
http://9GuhLTNd.zstry.cn
http://Qwgilwrm.zstry.cn
http://ZIzp4BoS.zstry.cn
http://lvIlkdDN.zstry.cn
http://tlYEbX2O.zstry.cn
http://342REGm6.zstry.cn
http://8Dugq4LO.zstry.cn
http://maZ20I4n.zstry.cn
http://r4Osd808.zstry.cn
http://KFJo5nm0.zstry.cn
http://troMmtsn.zstry.cn
http://mwnB5f0E.zstry.cn
http://ila4lmkb.zstry.cn
http://GzlJsF1N.zstry.cn
http://YynISGG9.zstry.cn
http://QKPkA4md.zstry.cn
http://TFFqx8GA.zstry.cn
http://AC89Gl7e.zstry.cn
http://5nGWS3ER.zstry.cn
http://akApXS4r.zstry.cn
http://www.dtcms.com/a/379531.html

相关文章:

  • (done) CUDA 和 CPU 性能对比,矩阵加法和矩阵乘法对比
  • 事实上事实上
  • 【左程云算法07】队列和栈-链表数组实现
  • 关于亚马逊账号关联的思考——关于侵权
  • 【硬件-笔试面试题-84】硬件/电子工程师,笔试面试题(知识点:MOS管是损耗有哪些)
  • mybatis vs mybatis-plus
  • 网络诊断和通信中非常重要的工具或协议
  • Mysql主键选取
  • 蓝桥杯嵌入式
  • Python学习——字典和文件
  • urllib的使用
  • AFSim2.9.0学习笔记 —— 4.1、创建项目,以此项目介绍工作中Wizard使用(红方/蓝方武器平台、阵营、更换图标等,多图详细介绍)
  • 机器人驭风而行:低空经济如何开启智能新纪元【科普类】
  • 【论文速读】LLM Compiler:并行函数调用的新范式
  • 【复习】计网每日一题---海明校验码
  • CVPR 2025最佳论文解读|VGGT:Visual Geometry Grounded Transformer
  • 深度学习里的树模型TabNet
  • 洛谷P5250 【深基17.例5】木材仓库 (集合法)详解
  • zsn的作品集
  • 磁共振成像原理(理论)6:自由感应衰减 (Free Induction Decays)
  • 第3节-使用表格数据-CHECK约束
  • 彻底解决Qt中文乱码以及汉字编码的问题(UTF-8/GBK)
  • 【观察】傅建平:迈向“数据强国”,打通数据要素化“任督二脉”的三把钥匙
  • 一些常用的CAPL小功能
  • 当Claude Code失灵,Qwen Code能否成为你的救星?
  • Spring 项目骨架
  • C++轻量级配置管理器升级版
  • WiFi CSI标准
  • 9、从水果店账本到AI大脑:矩阵运算如何驱动现代人工智能?零基础完全指南(Transformer数学原理)
  • 外部碎片和内部碎片