[牛客BM70&LeetCode322]零钱兑换Ⅰ——DFS,记忆化搜索,动态规划(C++)
题目描述
给你一个整数数组arr,表示不同面额的硬币;以及一个整数aim,表示需要放入钱包的目标金额。
计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1 。
每种硬币的数量无限。
- 用例1:
输入:[1, 2, 3], 6
输出:2(即3+3)
思路一:深度优先搜索
本题自然可以通过遍历所有可能的硬币组合以求得最少的硬币数量。
每次都选择三种面额(以用例1举例)中的一枚放入到钱包中,直到钱包达到目标金额。
上面这个思路其实就是深度优先搜索的方法(DFS)。递归深度就是使用的硬币的个数。
然而这种方式将会出现大量的重复计算,比如用例中:6-2=4,6-1-1=4;导致4这个节点会被多次计算。
如图
值为4的节点和值为3的节点都多次出现。
参考代码如下:
class Solution {
private:
int _min_depth = INT_MAX; // 初始化递归深度为(近似)无限大
public:
void dfs(vector<int> &arr, int rem, int depth) {
if (rem == 0) {
_min_depth = min(_min_depth, depth); // 每次递归到结束条件就更新最小的深度
return ;
}
for (auto coin : arr)
dfs(arr, rem - coin, depth + 1); // 每次递归深度+1
}
int minMoney(vector<int>& arr, int aim) {
dfs(arr, aim, 0);
return _min_depth;
}
};
思路二:记忆化搜索
根据上文分析,可以将已经出现过的钱包剩余目标金额的最少硬币数记录下来,应该使用数组记录1 ~ aim范围内所有的金额的最少硬币数。
class Solution {
public:
int dfs(vector<int> &arr, vector<int> &mem, int aim) {
if (aim < 0) return -1;
if (aim == 0) return 0;
if (mem[aim] <= aim) return mem[aim];
for (int i = 0; i < arr.size(); ++i) {
int temp = dfs(arr, mem, aim-arr[i]);
if (temp != -1)
mem[aim] = min(mem[aim], temp + 1);
}
if (mem[aim] <= aim)
return mem[aim];
else
return mem[aim] = -1;
}
int minMoney(vector<int>& arr, int aim) {
vector<int> mem(aim + 1, aim + 1); // 初始化数组
return dfs(arr, mem, aim);
}
};
思路三:动态规划
深度优先搜索采用自上而下的方法,“长”成了一棵树。
动态规划则是自下而上,找到每个节点的最小硬币数,不会是一个树状结构。
class Solution {
public:
int minMoney(vector<int>& arr, int aim) {
vector<int> dp(aim + 1, aim + 1);
dp[0] = 0;
for (int i = 1; i <= aim; ++i) {
for (int j : arr) {
if (i >= j)
dp[i] = min(dp[i], dp[i - j] + 1);
}
}
if (dp[aim] != aim + 1) return dp[aim];
else return -1;
}
};
错误的思路:贪心+DFS
有种错误的思路是,认为放尽量多的面额大的硬币就可以让使用硬币的数量最少(即贪心思想)。如果每种面额的硬币只有一枚,那么可以使用贪心思想。
然而,考虑这种情况:如目标金额10,硬币为7,5,1;则最少硬币数是2(5+5)而不是4(7+1+1+1)。
这里也提供错误思路的代码:
class Solution {
private:
int _min_depth = INT_MAX;
int flag = false;
public:
void dfs(vector<int>& arr, int rem, int depth) {
if (rem == 0) {
flag = true;
_min_depth = depth;
return ;
}
for (auto coin : arr) {
if (!flag) dfs(arr, rem - coin, depth + 1);
}
}
int minMoney(vector<int>& arr, int aim) {
sort(arr.begin(), arr.end(), greater<>());
dfs(arr, aim, 0);
return _min_depth == INT_MAX ? -1 : _min_depth;
}
};