class Solution {
public:
int waysToChange(int n) {
}
};
相当于给定一个面值数组,其中的值都是正数且没有重复。再给定一个正数aim。每个值都认为是一种面值,且认为张数是无限的。返回组成aim的最少货币数。
为什么要对 dp[idx][rest] %= 1000000007;
既然有一些硬币面值,那么我们就先用一个数组,将所有硬币的面值存储起来。注意,这里有一个潜规则:硬币在数组中的顺序,最好按照大小排序,升序和降序都可以
int[] coins = {1, 5, 10, 25}; //升序排列
还有一个情况:假设n = 10,硬币的组合方式有一种是1+1+1+1+1+5,这等价于1+5+1+1+1+1。这两种其实是一种情况。从这里我们可以推测出,在使用硬币的时候,相同的硬币最好放在一起使用,也就是不要出现上面一个5,将1分割成了两部分,如下图:
那怎么做呢?应该这样做:在使用一种硬币的时候,保证一次性拿够(数量)
算法如下:
class Solution {
long process(std::vector<int> &coins, int idx, int rest){
int N = coins.size();
if(idx == N){
return rest == 0 ? 1 : 0;
}
long ways = 0;
for (int zhang = 0; zhang * coins[idx] <= rest; ++zhang) {
ways += process(coins, idx + 1, rest - zhang * coins[idx]);
}
return ways;
}
public:
int waysToChange(int aim) {
if(aim < 0){
return -1;
}
std::vector<int> coins{1, 5, 10, 25};
return (int)process(coins, 0, aim);
}
};
以上就是递归的版本,核心就在于,相对于每一种硬币来说,我可以选择不要,又或者是要1个,或者是2个……,以这样的方式,一个一个的去尝试,这非常的暴力。
(1)准备一个数组。先来分析递归函数的可变参数以及变化范围
long process(std::vector<int> &coins, int idx, int rest)
有两个变化维度,所以准备一个二维数组:
long dp[N + 1][aim + 1];
(2)返回值。分析主函数值怎么调用递归函数的
return (int)process(coins, 0, aim);
所以应该返回dp[0][aim]
(3)填表
(3.1)使用base case 初始化表
if(idx == N){
return rest == 0 ? 1 : 0;
}
(3.3)根据依赖填写表的其他部分
long ways = 0;
for (int zhang = 0; zhang * coins[idx] <= rest; ++zhang) {
ways += process(coins, idx + 1, rest - zhang * coins[idx]);
}
return ways;
for (int idx = N - 1; idx >= 0; --idx) {
for (int rest = 0; rest <= aim; ++rest) {
}
}
(4)综上,递归函数如下
class Solution {
public:
int waysToChange(int aim) {
if(aim < 0){
return -1;
}
std::vector<int> coins{1, 5, 10, 25};
int N = coins.size();
std::vector<std::vector<long>> dp(N + 1, std::vector<long>(aim + 1, 0));
dp[N][0] = 1;
for (int idx = N - 1; idx >= 0; --idx) {
for (int rest = 0; rest <= aim; ++rest) {
dp[idx][rest] = dp[idx + 1][rest];
if(rest - coins[idx] >= 0){
dp[idx][rest] += dp[idx][rest - coins[idx]];
}
}
}
return dp[0][aim];
}
};
其实上面写的dp,只是较为经典的写法,本质上,这段代码还是存在一定的小问题,那就是存在枚举问题,也就是说,效率还不够高。还可以进行优化。
(这里应该有一张表格)
从上面动态规划填表中可以看出:
假设rest = 7时
从而:
推而广之:
class Solution {
public:
int waysToChange(int aim) {
if(aim < 0){
return -1;
}
std::vector<int> coins{1, 5, 10, 25};
int N = coins.size();
std::vector<std::vector<int>> dp(N + 1, std::vector<int>(aim + 1, 0));
dp[N][0] = 1;
for (int idx = N - 1; idx >= 0; --idx) {
for (int rest = 0; rest <= aim; ++rest) {
dp[idx][rest] = dp[idx + 1][rest];
if(rest - coins[idx] >= 0){
dp[idx][rest] += dp[idx][rest - coins[idx]];
}
}
}
return dp[0][aim];
}
};
所以如果单独一个格子计算没有枚举行为(for循环),那么它们的复杂度是一样的;但是如果有for枚举,那么需要弄出严格表结构之后继续优化