最少硬币数的找零方案(动态规划)
题目描述:
给定需要找零的面值m和可以使用的硬币种类n(假设每种硬币有无限多个),求出用这些硬币找零m所需要的最少硬币数。
输入描述:
第一行包含两个整数:m ,n,意义如题目描述。接下来的 n 行,每行一个整数,第 i + 1 行的整数表示第 i 种硬币的面值。
输出描述:
输出一个整数,表示最少需要携带的硬币数量。如果无解,则输出-1。
分析:
我们用 d(i) = j 来表示找零 i 元最少需要 j 个硬币。于是我们得到 d(0) = 0, 表示找零0元最少需要0个硬币。
当 i = 1 时,只有面值为1的硬币可用,有:
d(1) = d(1 - 1) + 1 = d(0) + 1 = 1
当 i = 2 时,可用硬币的面值是1、2,有:
d(2) = min{ d(2 - 1) + 1, d(2 - 2) + 1 } = min{ d(1) + 1, d(0) + 1 } = 1
当 i = 3 时,可用硬币的面值是1、2,有:
d(3) = min{ d(3 - 1) + 1, d(3 - 2) + 1 } = min{ d(2) + 1, d(1) + 1 } = 2
当 i = 4 时,可用硬币的面值是1、2,有:
d(4) = min{ d(4 - 1) + 1, d(4 - 2) + 1 } = min{ d(3) + 1, d(2) + 1 } = 2
当 i = 5 时,可用硬币的面值是1、2、5,有:
d(5) = min{ d(5 - 1) + 1, d(5 - 2) + 1, d(5 - 5) + 1 } = min{ d(4) + 1, d(3) + 1, d(0) + 1 } = 1
……
显然用动态规划实现这一过程,代码如下。
代码:
1 #include<iostream> 2 #include<algorithm> 3 #include<vector> 4 using namespace std; 5 6 int main() 7 { 8 int m = 0; // 要找零的面值 9 int n = 0; // 不同面值的硬币种类 10 vector<int> coin; // 不同面值的硬币 11 vector<int> status; // status[i]表示找零面值i最少需要的硬币数 12 cin >> m >> n; 13 for (int i = 0; i < n; i++){ 14 int temp; 15 cin >> temp; 16 coin.push_back(temp); 17 } 18 sort(coin.begin(), coin.end()); // 硬币按面值升序排列 19 reverse(coin.begin(), coin.end()); // 硬币按面值降序排列 20 status = vector<int>(m + 1, 0); // 初始化为0 21 for (int i = 1; i <= m; i++) 22 { 23 vector<int> min; 24 for (auto it = coin.begin(); it != coin.end(); it++) 25 { 26 // 硬币恰好找零 27 if (i == *it) 28 { 29 min.push_back(1); 30 break; // 跳出循环,结束寻找 31 } 32 // 硬币可以找开 并且 i - *it 有找零方案 33 else if (i > *it && status[i - *it] > 0) 34 { 35 min.push_back(status[i - *it] + 1); // 存入用*it找零需要的硬币数 36 } 37 // 硬币找不开 38 else 39 continue; 40 } 41 if (min.size() > 0) 42 status[i] = *min_element(min.begin(), min.end()); // 找到最少的硬币数 43 else 44 status[i] = -1; // 找不开 45 } 46 cout << *(status.end() - 1) << endl; // 输出找零m需要的最少硬币数 47 //for (int i = 0; i < status.size(); i++) 48 //{ 49 // cout << i << "\t" << status[i] << endl; // 输出找零i(0<=i<=m)需要的最少硬币数 50 //} 51 system("pause"); 52 return 0; 53 }
注意33行的代码,双重判断:
else if (i > *it && status[i - *it] > 0)
第一个判断,确定 *it 可以找零,第二个判断,确定用 *it 找零后,i - * it 也能够找零。
测试:
1. 用3种硬币1、2、5找零20,需要4个硬币(4个5)。
2. 用3种硬币2、3、5找零11,需要3个硬币(5、3、3)。
附:
如果想分别输出面值1~m的最少找零硬币数,并给出对应的找零方案呢?代码稍加修改即可,见下。
1 #include<iostream> 2 #include<algorithm> 3 #include<vector> 4 using namespace std; 5 6 int main() 7 { 8 int m = 0; // 要找零的面值 9 int n = 0; // 不同面值的硬币种类 10 vector<int> coin; // 不同面值的硬币 11 vector<int> status; // status[i]表示找零面值i最少需要的硬币数 12 vector<vector<int>> trace; // trace[i]表示找零面值i用到的硬币面值 13 const int maxInt = 0x7FFFFFFF; 14 cin >> m >> n; 15 for (int i = 0; i < n; i++){ 16 int temp; 17 cin >> temp; 18 coin.push_back(temp); 19 } 20 sort(coin.begin(), coin.end()); // 硬币按面值升序排列 21 reverse(coin.begin(), coin.end()); // 硬币按面值降序排列 22 status = vector<int>(m + 1, 0); // 初始化为0 23 trace = vector<vector<int>>(m + 1); 24 for (int i = 1; i <= m; i++) 25 { 26 int min = maxInt; 27 for (auto it = coin.begin(); it != coin.end(); it++) 28 { 29 // 硬币恰好找零 30 if (i == *it) 31 { 32 min = 1; 33 trace[i].push_back(*it); 34 break; // 跳出循环,结束寻找 35 } 36 // 硬币可以找开 37 else if (i > *it && status[i - *it] > 0) 38 { 39 if (min > status[i - *it] + 1) 40 { 41 min = status[i - *it] + 1; // 存入用*it找零需要的硬币数 42 trace[i].clear(); 43 for (auto &it1 : trace[i - *it]) 44 trace[i].push_back(it1); 45 trace[i].push_back(*it); 46 } 47 } 48 // 硬币找不开 49 else 50 continue; 51 } 52 if (min == maxInt) 53 status[i] = -1; // 找不开 54 else 55 status[i] = min; // 更新最少的硬币数 56 } 57 //cout << *(status.end() - 1) << endl; // 输出找零m需要的最少硬币数 58 for (int i = 1; i < status.size(); i++) 59 { 60 cout << i << "\t" << status[i] << "\t"; // 输出找零i(1<=i<=m)需要的最少硬币数 61 for (auto &it : trace[i]) // 输出找零i(1<=i<=m)需要最少硬币数的硬币组合 62 cout << it << "\t"; 63 cout << endl; 64 } 65 system("pause"); 66 return 0; 67 }
测试:
20 4
1
2
5
10
100 5
3
7
9
11
17