携带研究材料(完全背包)
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的重量,并且具有不同的价值。
小明的行李箱所能承担的总重量是有限的,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料可以选择无数次,并且可以重复选择。
输入描述
第一行包含两个整数,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.
相比于01背包,所有的物品都可以无限次放进背包。
dp[i][j]:表示取从0到i的物品,放进容量为j的背包里,背包的最大价值。
递推公式:如果容量j<当前物品重量 dp[i][j] = dp[i-1][j];
如果容量j>=当前物品重量 dp[i][j] = max(dp[i-1][j] ,dp[i][j-w[i]]+v[i]);
(如果是01背包就是dp[i][j] = max(dp[i-1][j] ,dp[i-1][j-w[i]]+v[i]))
初始化:j为0的dp全初始化为0;i为0的,当j<w[0],初始化为0,else,看看能装几个w[0],dp就加几个v[i]
递归顺序 我使用的 先物品后背包,均为从前往后。
感觉相比于01背包,只有递推公式和初始化发生了变化。
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n,bagWeight;
cin>>n>>bagWeight;
vector<int> w(n);
vector<int> v(n);
for(int i = 0;i<n;i++)
{
cin>>w[i];
cin>>v[i];
}
vector<vector<int>> dp(n,vector<int>(bagWeight+1,0));
for(int i = w[0];i<=bagWeight;i++)
{
dp[0][i] = dp[0][i-w[0]]+v[0];
}
for(int i = 1;i<n;i++)
{
for(int j = 0;j<=bagWeight;j++)
{
if(j<w[i]) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
}
}
cout<<dp[n-1][bagWeight]<<endl;
return 0;
}
零钱兑换II
cpp
给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
示例 3:
输入:amount = 10, coins = [10]
输出:1
提示:
1 <= coins.length <= 300
1 <= coins[i] <= 5000
coins 中的所有值 互不相同
0 <= amount <= 5000
dp[i][j]:表示只用0到i种面额的硬币,凑成总金额为j的方法共有dp[i][j]种。
递归公式: 如果容量j<当前物品重量 dp[i][j] = dp[i-1][j];
如果容量j>=当前物品重量 dp[i][j] = dp[i-1][j] +dp[i][j-w[i]]+v[i];
初始化:j = 0 dp为1;i = 0,j是coins[0]的倍数就初始化为1,其他为0.
遍历顺序:同上。
但是这个题如果使用dp的二维数组,时间复杂度会很高,是Nm。建议使用滚动数组。但这次我就只给出二维数组的形式了,等二刷再使用滚动数组。
cpp
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<vector<uint64_t>> dp(coins.size(),vector<uint64_t>(amount+1,0));
for(int j = coins[0];j<=amount;j++)
{
if(j%coins[0] == 0)dp[0][j] = 1;
else dp[0][j] = 0;
}
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<=amount;j++)
{
if(j<coins[i]) 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][amount];
}
};
注意:本题由于测试点,我们使用uint64_t代替int,下面给出二者区别:
1. 类型定义和大小
uint64_t
:
- 来自
<cstdint>
头文件,表示一个无符号 64 位整数。- 大小固定为 64 位(8 字节),无论平台或编译器。
int
:
- 标准有符号整数类型,大小通常为 32 位(4 字节),但取决于平台(例如,在 32 位系统中可能是 32 位,64 位系统中也可能保持 32 位)。
- 大小不固定,可能导致跨平台兼容性问题。
2. 取值范围
uint64_t
:
- 范围:0 到 18,446,744,073,709,551,615(即 264−1)。
- 只能表示非负整数(无负数)。
int
:
- 范围通常为 -2,147,483,648 到 2,147,483,647(即 −231 到 231−1),但实际范围取决于实现。
- 可以表示正数、负数和零。
3. 符号性
uint64_t
是无符号(unsigned),适用于不需要负数的场景(如计数器、ID、位掩码)。int
是有符号(signed),适用于需要正负值的通用整数运算。4. 在
vector<vector<uint64_t>>
中的影响
- 使用
uint64_t
的优势 :
- 避免溢出:适合存储非常大的值(如文件大小、时间戳或大数组索引),
int
的较小范围可能导致溢出错误(例如,值超过 2 亿时)。- 确保非负性:强制所有元素为非负数,减少负值导致的逻辑错误(如无效索引)。
- 跨平台一致性:
uint64_t
的大小固定,保证代码在不同系统上行为一致。- 性能:在 64 位系统上,64 位整数操作通常高效,但占用更多内存(每个元素 8 字节 vs
int
的通常 4 字节)。- 使用
int
的可能场景 :
- 如果数据范围小(例如 -100 到 100),
int
更节省内存(每个元素少 4 字节)。- 需要负值时,
int
更合适(但uint64_t
不支持负数)。int
在通用代码中更常见,但可能因平台差异导致错误。5. 示例场景
- 如果您在
vector<vector<uint64_t>>
中存储大型数据集(如图像像素值、科学计算中的大整数),uint64_t
更可靠。- 如果数据较小或需负值(如温度读数),使用
int
可能更高效(但需检查范围)。
组合总和 Ⅳ
给你一个由 不同 整数组成的数组
nums
,和一个目标整数target
。请你从nums
中找出并返回总和为target
的元素组合的个数。题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4 输出:7 解释: 所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1) 请注意,顺序不同的序列被视作不同的组合。
示例 2:
输入:nums = [9], target = 3 输出:0
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 1000
nums
中的所有元素 互不相同1 <= target <= 1000
首先我们要知道本题是要求的是排列数。
而 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
由此,我们只需要再上题的基础上,把循环顺序进行调整即可。
可就会出现如下问题:
. 状态定义错误(核心问题)
- 您的二维DP数组
dp[i][j]
表示使用前i+1
个数字组成目标j
的方案数,这本质上是组合问题(不考虑顺序)。- 但题目要求计算排列数 (顺序不同的序列视为不同),例如
(1,1,2)
和(1,2,1)
应算作两种方案。- 错误原因:二维DP的内外循环固定了数字顺序(先遍历数字再遍历容量),无法生成不同排列。
因此我需要定义一个一维的Dp数组来解决问题。
dp[j]:表示容量为j大小的背包所具有的组合数。
初始化:Dp[0]=1 其他均为0.
遍历顺序:先遍历容量,在遍历物品。
递推公式,只要容量大于当先物品的大小,就+= dp[j-nums[i]]。
cpp
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<uint64_t> dp(target+1,0);
dp[0] = 1;
for(int j = 0;j<=target;j++)
{
for(int i = 0;i<nums.size();i++)
{
if(j >= nums[i])
dp[j] += dp[j-nums[i]];
}
}
return dp[target];
}
};
爬楼梯
题目描述
假设你正在爬楼梯。需要 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 阶
本题基本思路同上题,完全背包,可以重复取,且要的是排排列数,则外层for遍历背包,内层遍历物品。
n就是你的背包容量,m是物品数量。
dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
cpp
#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;
}
}