📟作者主页:慢热的陕西人
🌴专栏链接:力扣刷题日记
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
文章目录
- 牛客热题:兑换零钱(一)
- 题目链接
- 方法一:动态规划
- 思路
- 代码
- 复杂度
- 方法二:递归
- 思路
- 总结思路:
- 代码
- 复杂度
牛客热题:兑换零钱(一)
题目链接
兑换零钱(一)_牛客题霸_牛客网 (nowcoder.com)
方法一:动态规划
思路
首先我们思考当arr中最小的零钱是1时,那么这时候是凑成aim的最大钱数,aim个1块钱。
①状态表示:
d p [ i ] dp[i] dp[i]表示的是i块钱用数组中的零钱表示的最少钱数。
②状态转化:
显然 d p [ i ] dp[i] dp[i]的值可以从 d p [ i − a r r [ j ] ] dp[i - arr[j]] dp[i−arr[j]]的状态获取,但是题目要求最小值,所以取一个min
d p [ i ] = m i n ( d p [ i ] , d p [ i − a r r [ j ] ] + 1 ) dp[i] = min(dp[i], dp[i - arr[j]] + 1) dp[i]=min(dp[i],dp[i−arr[j]]+1)
③填表顺序:
对于dp数组的话,因为dp的当前状态需要用到之前状态,所以按照从左的到右的顺序
对于原数组arr,这个数组的话不牵扯到状态转移,只需要遍历就好了,习惯上也从左到右来遍历
④初始化:
对于dp数组,开始时除了 d p [ 0 ] = 0 dp[0] = 0 dp[0]=0,其余的dp状态都应该设定为对应的最大值+1,也就是aim + 1;
⑤返回值:
代码
int minMoney(vector<int>& arr, int aim)
{
//创建dp数组
vector<int> dp(aim + 1, aim + 1);
dp[0] = 0;
for(int i = 0; i <= aim; ++i)
{
for(int j = 0; j < arr.size(); ++j)
{
if(i >= arr[j])
dp[i] = min(dp[i], dp[i - arr[j]] + 1);
}
}
return dp[aim] > aim ? -1 : dp[aim];
}
复杂度
时间复杂度: O ( a i m ∗ a r r . s i z e ( ) ) O(aim * arr.size()) O(aim∗arr.size()), 遍历dp数组的同时去遍历原数组。
空间复杂度: O ( a i m + 1 ) O(aim + 1) O(aim+1),额外使用了一个dp数组,长度是aim+1。
方法二:递归
思路
-
recursion 函数:
- 这是一个递归函数,用于计算组合达到目标金额
aim
所需的最小硬币数量。 - 参数包括
arr
(硬币面值数组)、aim
(目标金额)、dp
(用于记录中间结果的数组)。
- 这是一个递归函数,用于计算组合达到目标金额
-
基础情况:
- 如果
aim < 0
,表示当前的组合方式超过了目标金额,返回-1
。 - 如果
aim == 0
,表示当前的组合方式正好等于目标金额,返回0
(硬币数量为0)。 - 如果
dp[aim - 1] != 0
,说明aim
这个金额已经计算过了,直接返回dp[aim - 1]
。
- 如果
-
递归求解:
- 初始化
Min
为INT_MAX
,用于记录当前情况下的最小硬币数量。 - 遍历所有硬币面值
arr[i]
,对于每个面值,计算使用该面值时剩余金额aim - arr[i]
的最小硬币数量res
。 - 如果
res >= 0
(说明可以组合成功)且res + 1
比当前Min
小,则更新Min
。
- 初始化
-
更新结果:
- 将
dp[aim - 1]
设置为Min
,如果Min
仍然是INT_MAX
,表示无法组合成aim
,返回-1
,否则返回Min
。
- 将
-
minMoney 函数:
- 这是主函数,初始化了
dp
数组并调用了recursion
函数来求解最小硬币数量。 - 如果
aim < 1
,直接返回0
,因为小于等于0
的金额不需要硬币。
- 这是主函数,初始化了
总结思路:
- 使用递归方法和动态规划思想,通过
dp
数组记录中间结果,避免重复计算,提高效率。 - 递归函数中每次选择一个硬币面值,尝试组合达到目标金额,找出最少的硬币数量。
- 如果某个金额已经计算过,直接返回已存储的结果,避免重复计算,提高效率。
代码
int recurison(vector<int>& arr, int aim, vector<int>& dp)
{
if(aim < 0) return -1;
if(aim == 0) return 0;
if(dp[aim - 1] != 0)
return dp[aim - 1];
int Min = INT_MAX;
//遍历所有的值
for(int i = 0; i < arr.size(); ++i)
{
//递归运算选择当前的面值
int res = recurison(arr, aim - arr[i], dp);
//获取最小值
if(res >= 0 && res < Min)
Min = res + 1;
}
//更新最小值
dp[aim - 1] = Min == INT_MAX ? -1 : Min;
return dp[aim - 1];
}
int minMoney(vector<int>& arr, int aim)
{
if(aim < 0) return -1;
vector<int> dp(aim, 0);
return recurison(arr, aim, dp);
}
复杂度
- 时间复杂度: O ( n ∗ a i m ) O(n * aim) O(n∗aim),一共需要计算aim个状态的答案,每个状态需要枚举n个面值
- 空间复杂度: O ( a i m ) O(aim) O(aim),递归栈深度及辅助数组的空间