【发布时间】:2020-04-25 03:24:59
【问题描述】:
我最近与一位同事讨论了以下(相当基本的)问题,并想知道如何优化它以及优化后的版本将具有什么运行时。
问题:
你必须用一套可用的硬币退还一定数量的钱。你有无限数量的硬币可用。如果无法返回此金额,则应返回错误。
示例 1:
一组硬币(本例中为欧元,单位为 ct){200, 100, 50, 20, 10, 5, 2, 1},返还金额99。
回答:50, 20, 20, 5, 2, 2
示例 2:
套币{50, 20, 2},返还金额99。
答案:no solution found!(显然,因为没有办法返回奇数!)。
我的解决方案:
(本文末尾的代码)。我的解决方案是一个贪婪的递归函数,但如果贪婪方法不起作用,它会尝试使用较小的硬币。最后,我基本上是以深度优先的方式遍历解空间(一棵树)。
在最坏的情况下(例如{97, 98, 99} 和300)找不到解决方案,我必须遍历整个树,这导致O(l^(a\s)),其中l 是硬币集合的长度( 8 在欧元的情况下,3 在这种情况下),a 是金额(示例中为 300),s 是可用的最小硬币(本示例中为 97),\ 是integer division。 如果我错了,请纠正我。
我正在寻找的优化:
您可以看到“解决方案树”中有一些不需要探索的路径。这是因为硬币的顺序无关紧要,但仍然会遍历不同的顺序(例如,99, 98, 97 和 98, 99, 97 都被遍历)。 如何以只遍历必要路径的方式修剪树?我正在考虑某种缓存,但无法想出一些聪明的方法。 这种解决方案的运行时间是多少?
C++17 代码:
#include <iostream>
#include <list>
#include <optional>
#include <string>
std::string print_list(std::optional<std::list<int>> lst)
{
if (lst)
{
std::string res = "";
for (int num : lst.value())
{
res += std::to_string(num) + ", ";
}
return res + "\n";
}
return "no solution found!\n";
}
std::optional<std::list<int>> get_coins(int remainder, std::list<int> ¤cy, std::list<int> coins)
{
if (remainder == 0)
{
return std::optional{coins};
}
for (int coin : currency)
{
if (coin <= remainder)
{
coins.push_back(coin);
auto result = get_coins(remainder - coin, currency, coins);
if (result)
{
return result;
}
}
}
return std::nullopt;
}
int main()
{
// Example 1
std::list<int> eur{200, 100, 50, 20, 10, 5, 2, 1};
std::cout << print_list(get_coins(99, eur, {})); // 50, 20, 20, 5, 2, 2,
// Example 2
std::list<int> fail{50, 20, 2};
std::cout << print_list(get_coins(99, fail, {})); // no solution found!
}
【问题讨论】:
-
为什么要搜索不同的订单?
-
我不想。我只想保持一种贪婪的方法,如果有一个解决方案,它总是会给我一个解决方案。我搜索它们是因为我正在探索整个解决方案空间,而不是因为我想要
-
您想更快地解决问题,而又不想避免最明显的时间浪费:明白了。
-
您是否仅在寻找解决方案或其他解决方案,可能与此不同,但会比您最初提出的解决方案更有效地完成工作?提示:可以尝试分割方法(取整个部分,忽略提示)或使用double for(嵌套;也可以用一个完成;我立即想出了双循环的代码)。
标签: c++ recursion tree runtime