26、零钱兑换
题目
26.0、记忆化搜索
其实就是暴力解法,比如对于金额20,硬币面值为【1,2,5】
看看15需要多少硬币,再看看10需要多少硬币,再看看5需要多少硬币。算出需要4枚
看看18需要多少硬币,再看看13需要多少硬币,再看看8需要多少硬币,再看看3需要多少硬币,再看看1需要多少硬币。算出6枚
看看19需要多少硬币,再看看14需要多少硬币,再看看9需要多少硬币,再看看4需要多少硬币,再看看2需要多少硬币。算出6枚
所以结果是4枚。
上述例子可以正好凑够金额,所以没有分支,如果金额是20,硬币面值是【2,5,7】呢
看看13需要多少硬币,再看看6需要多少硬币,再看看1需要多少硬币。在这里出现分支,因为1不能凑够,所以需要回退
-------------------------------------------------------------,再看看4需要多少硬币,再看看2需要多少硬币。算出5枚
其实这种方法展开来看就是一个树,这个树把所有情况都包括进去了,也就是暴力了
这种做法其实是超时的,但是可以用一个数组来记录一些面值所需要的硬币数,来减少时间开销,但是还不如直接使用下面的动态规划来得简单,因为这样的话本质上就和动态规划一样了,只不过把迭代变成了递归。
时间复杂度:O(S n)S指的是金额,n指的是硬币的种类
空间复杂度:O(S)需要S大小的空间来优化原本的暴力
26.1、动态规划
我们来设F(x) = x的金额最少需要硬币的数量,硬币序列为【1,2,5】
那状态转移方程就是F(x) = min【F(x-1),F(x-2),F(x-5)】+1
哦了,就这。
//这是俺的垃圾解法
int coinChange(vector<int>& coins, int amount)
{
if (!amount)return 0;
vector<int> data(amount + 1, -1);//创建大小为amount+1的数组
for (auto& i : coins)
if (i <= amount)data[i] = 1;//将每一硬币面值的对应的下标的值都记为1
for (int i = 1; i <= amount; i++)
{
if (data[i] == 1)continue;
int preMinWays = INT_MAX;
for (auto& j : coins)
if (j < i && data[i - j] != -1)preMinWays = min(preMinWays, data[i - j]);
if (preMinWays != INT_MAX)
data[i] = preMinWays + 1;
}
return data[amount];
}
时间复杂度:O(S n)S指的是金额,n指的是硬币的种类
空间复杂度:O(S)需要S大小的空间来进行记录
下面是代码的优化
//俺滴垃圾代码的优化
int coinChange2(vector<int>& coins, int amount)
{
vector<int> data(amount + 1, amount + 1);
data[0] = 0;
for (int i = 1; i <= amount; i++)
for (auto& j : coins)
if (j <= i)data[i] = min(data[i], data[i - j] + 1);
return data[amount] > amount ? -1 : data[amount];
}
性能是不变的,但是在实际的测试时候,下面这个总是慢一点(俺也不知道是为什么)。