上周我参加了面试,我被要求为通用ATM算法编写代码。
所以我必须实现功能ATM,它可以获取所有可用的钞票,并且有限额和所需的金额,并以任何可能的方式返回给定钞票及其限额的这笔钱。
ATM功能实现的第一次迭代非常简单,因为限制看起来像
{
1000: 20,
500: 430,
100: 300,
50: 23,
10: 23
}
但是在任务限制的最后一次迭代中看起来像
{
1000: 20,
500: 430,
100: 300,
50: 23,
30: 23
}
我想,我成功实现了这个算法的工作版本,但我对最终的复杂性不满意,我觉得应该有更高效的东西。
我的决定如下:
尝试在每个子集中形成所需的金额,所以如果我必须处理 有180个金额,我会尝试形成这个数额
从我喜欢的成功变体中选择并将其作为结果返回。
我在JavaScript中的决定的工作代码如下:
function getAllSubsetsInSet(set) {
const result = [];
let mask = 1;
do {
let maskString = mask.toString(2).padStart(set.length, '0');
result.push(set.filter((item, index) => maskString[index] === '1'));
mask++;
}while (mask < (2 ** set.length));
return result;
}
function getMoney(currencies, limits, amount) {
const sorted = currencies.sort((a, b) => b - a);
let workingLimits = {
...limits
};
let workingAmount = amount;
let result = {};
for (let i = 0; i < sorted.length; i++) {
let currentCurrency = sorted[i];
let desiredBanknotes = Math.floor(workingAmount / currentCurrency);
let availableBanknotes = workingLimits[currentCurrency];
let banknotesToBeUsed = (availableBanknotes < desiredBanknotes) ? availableBanknotes : desiredBanknotes;
workingAmount = (workingAmount - (banknotesToBeUsed * currentCurrency));
workingLimits[currentCurrency] = availableBanknotes - banknotesToBeUsed;
result[currentCurrency] = banknotesToBeUsed;
}
if (workingAmount > 0) {
return {
result: {},
limits,
error: true
}
}
return {
result: result,
limits: workingLimits,
error: false
}
}
function ATM(limits, amount) {
let currencies = Object.keys(limits).map(item => Number(item));
let allCurrencyCombinations = getAllSubsetsInSet(currencies);
let resultsForEachCombination = allCurrencyCombinations.map(combination => {
return getMoney(combination, limits, amount);
});
const succeedResults = resultsForEachCombination.filter(variant => !variant.error);
if (succeedResults.length) {
return succeedResults;
}
return {
result: 'No possible ways',
limits
}
}
console.log(ATM(
{
1000: 20,
500: 430,
100: 300,
50: 23,
30: 90
},
180
));
但是,请问,任何人都可以用正确和有效的方式来帮助我实现这个逻辑吗?任何编程语言和伪代码都可以。
谢谢!
答案 0 :(得分:1)
有一些更高效的东西。只需编写一个递归算法,尽可能多地尝试大量钞票的贪婪。如果有答案,通常会很快找到答案。但失败会很慢。
在一次采访中,我只是用性能说明给出答案,并表明如果需要,我知道如何加快速度。
如果他们问如何加快速度,你首先要寻找任何能够将大钞票视为小钞票的机会,因为有足够的小钞票可以将大钞票划分为小钞票的任何倍数。 。一旦你用较少的面额解决版本,那么你可以贪婪地扭转这个过程,找出你实际分发的账单。
在你的第一种情况下,你将你减少到一堆10秒,答案立竿见影。不需要递归。
第二种情况更有趣。当您开始时,您可能需要考虑21 * 431 * 301 * 24 * 24 = 1_569_226_176
种可能的笔记组合。 (请注意,每个因子都有+1,因为你可以有0到n。还要注意你可以修剪搜索空间以避免考虑很多。但数量会很大。)
但是你可以在心理上用2500个音符取代1000个音符,因为你有500个音符。然后用100个音符替换500个音符,因为你有4 100个音符。 (注意,可分的-1是因为你不需要能够用100个音符制作500个。只需要值0,100,200,300和400.)再次100个音符有50个音符。但后来我们停止了,因为30个音符不会划分50个音符。这转变
{
1000: 20,
500: 430,
100: 300,
50: 23,
30: 23
}
进行改变:
{
50: 5323,
30: 23
}
这会更快,因为需要考虑的组合较少。最糟糕的情况5324 * 24 = 127_776
但它仍然没有我们能够获得的那么快。
最后的性能提升是用一些小账单和一大堆账单来代替小额账单。在这种情况下,30和50的最小公倍数是150.所以我们实际上可以把它当作一些杂散音符来进行最后的改变,以及整整150个音符。
{
150: 178, // 174 from 50s, 4 from 30s
50: 1,
30: 3
}
现在我们必须做很少的工作才能弄清楚我们是否可以做出改变。 (简单蛮力的原始可能性低至179 * 2 * 4 = 1432
。如果你现在稍微聪明一点,你只需看看其中的8个。)一旦我们改变了,我们只是扭转过程以回到我们所拥有的实际面额。
这种优化版本的面额总能快速找到答案,我们可以从中反复查找实际账单。