作者热门文章
- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我在一次培训中遇到了这个问题。即我们给了 N
不同的值( N<= 100
)。让我们将这个数组命名为 A[N]
,对于这个数组 A,我们确定数组中有 1 并且 A[i]
≤ 109。 其次,我们给出了数字 S
,其中 S
≤ 109。
现在我们必须用这个值解决经典的硬币问题。实际上,我们需要找到与 S
相加的最小元素数。 A
中的每个元素都可以无限次使用。
S = 1000, N = 10
A[] = {1,12,123,4,5,678,7,8,9,10}. The result is 10.
1000 = 678 + 123 + 123 + 12 + 12 + 12 + 12 + 12 + 12 + 4
S = 1000000000 N = 100
1 373241370 973754081 826685384 491500595 765099032 823328348 462385937
251930295 819055757 641895809 106173894 898709067 513260292 548326059
741996520 959257789 328409680 411542100 329874568 352458265 609729300
389721366 313699758 383922849 104342783 224127933 99215674 37629322
230018005 33875545 767937253 763298440 781853694 420819727 794366283
178777428 881069368 595934934 321543015 27436140 280556657 851680043
318369090 364177373 431592761 487380596 428235724 134037293 372264778
267891476 218390453 550035096 220099490 71718497 860530411 175542466
548997466 884701071 774620807 118472853 432325205 795739616 266609698
242622150 433332316 150791955 691702017 803277687 323953978 521256141
174108096 412366100 813501388 642963957 415051728 740653706 68239387
982329783 619220557 861659596 303476058 85512863 72420422 645130771
228736228 367259743 400311288 105258339 628254036 495010223 40223395
110232856 856929227 25543992 957121494 359385967 533951841 449476607
134830774
OUTPUT FOR THIS TEST CASE: 5
S = 999865497 N = 7
1 267062069 637323855 219276511 404376890 528753603 199747292
OUTPUT FOR THIS TEST CASE: 1129042
S = 1000000000 N = 40
1 12 123 4 5 678 7 8 9 10 400 25 23 1000 67 98 33 46 79 896 11 112 1223 412
532 6781 17 18 19 170 1400 925 723 11000 607 983 313 486 739 896
OUTPUT FOR THIS TEST CASE: 90910
最佳答案
(注意:为了清晰起见进行了更新和编辑。最后添加了复杂性分析。)
好的,这是我的解决方案,包括我对@PeterdeRivaz 发现的性能问题的修复。我已经针对问题和评论中提供的所有测试用例对此进行了测试,并且它在一秒钟内完成(好吧,在一个案例中为 1.5 秒),主要仅使用部分结果缓存的内存(我猜约 16MB)。
我没有使用传统的 DP 解决方案(它既太慢又需要太多内存),而是使用深度优先、贪婪优先的组合搜索,并使用当前最佳结果进行修剪。我很惊讶(非常)这和它一样有效,但我仍然怀疑您可以构建需要最坏情况指数时间的测试集。
首先有一个主函数,它是调用代码唯一需要调用的东西。它处理所有设置和初始化并调用其他所有内容。 (所有代码都是C#)
// Find the min# of coins for a specified sum
int CountChange(int targetSum, int[] coins)
{
// init the cache for (partial) memoization
PrevResultCache = new PartialResult[1048576];
// make sure the coins are sorted lowest to highest
Array.Sort(coins);
int curBest = targetSum;
int result = CountChange_r(targetSum, coins, coins.GetLength(0)-1, 0, ref curBest);
return result;
}
// implement a very simple cache for previous results of remainder counts
struct PartialResult
{
public int PartialSum;
public int CoinVal;
public int RemainingCount;
}
PartialResult[] PrevResultCache;
// checks the partial count cache for already calculated results
int PrevAddlCount(int currSum, int currCoinVal)
{
int cacheAddr = currSum & 1048575; // AND with (2^20-1) to get only the first 20 bits
PartialResult prev = PrevResultCache[cacheAddr];
// use it, as long as it's actually the same partial sum
// and the coin value is at least as large as the current coin
if ((prev.PartialSum == currSum) && (prev.CoinVal >= currCoinVal))
{
return prev.RemainingCount;
}
// otherwise flag as empty
return 0;
}
// add or overwrite a new value to the cache
void AddPartialCount(int currSum, int currCoinVal, int remainingCount)
{
int cacheAddr = currSum & 1048575; // AND with (2^20-1) to get only the first 20 bits
PartialResult prev = PrevResultCache[cacheAddr];
// only add if the Sum is different or the result is better
if ((prev.PartialSum != currSum)
|| (prev.CoinVal <= currCoinVal)
|| (prev.RemainingCount == 0)
|| (prev.RemainingCount >= remainingCount)
)
{
prev.PartialSum = currSum;
prev.CoinVal = currCoinVal;
prev.RemainingCount = remainingCount;
PrevResultCache[cacheAddr] = prev;
}
}
/*
* Find the minimum number of coins required totaling to a specifuc sum
* using a list of coin denominations passed.
*
* Memory Requirements: O(N) where N is the number of coin denominations
* (primarily for the stack)
*
* CPU requirements: O(Sqrt(S)*N) where S is the target Sum
* (Average, estimated. This is very hard to figure out.)
*/
int CountChange_r(int targetSum, int[] coins, int coinIdx, int curCount, ref int curBest)
{
int coinVal = coins[coinIdx];
int newCount = 0;
// check to see if we are at the end of the search tree (curIdx=0, coinVal=1)
// or we have reached the targetSum
if ((coinVal == 1) || (targetSum == 0))
{
// just use math get the final total for this path/combination
newCount = curCount + targetSum;
// update, if we have a new curBest
if (newCount < curBest) curBest = newCount;
return newCount;
}
// prune this whole branch, if it cannot possibly improve the curBest
int bestPossible = curCount + (targetSum / coinVal);
if (bestPossible >= curBest)
return bestPossible; //NOTE: this is a false answer, but it shouldnt matter
// because we should never use it.
// check the cache to see if a remainder-count for this partial sum
// already exists (and used coins at least as large as ours)
int prevRemCount = PrevAddlCount(targetSum, coinVal);
if (prevRemCount > 0)
{
// it exists, so use it
newCount = prevRemCount + targetSum;
// update, if we have a new curBest
if (newCount < curBest) curBest = newCount;
return newCount;
}
// always try the largest remaining coin first, starting with the
// maximum possible number of that coin (greedy-first searching)
newCount = curCount + targetSum;
for (int cnt = targetSum / coinVal; cnt >= 0; cnt--)
{
int tmpCount = CountChange_r(targetSum - (cnt * coinVal), coins, coinIdx - 1, curCount + cnt, ref curBest);
if (tmpCount < newCount) newCount = tmpCount;
}
// Add our new partial result to the cache
AddPartialCount(targetSum, coinVal, newCount - curCount);
return newCount;
}
O(N)
,所以放在一起,内存显然不是问题。
O(N^S)
,这显然太高了。我认为更公平的估计是
O(N^(S/N))
或可能
O(N^(S/AVG(N))
甚至
O(N^(S/(Gmean(N)))
哪里
Gmean(N)
是 N[] 元素的几何平均值。该解决方案从蛮力组合搜索开始,然后通过两个重要的优化对其进行改进。
O(Log(N^(S/N))
, 或
O(N*Log(S/N))
这非常好。 (虽然
O(N*Log(S/Gmean(N)))
可能更准确)。
|(S/n2 - S/n1)| > 1
对于 N 中的任意 2 个值,它几乎变得非常有效。对于小于 SQRT(S) 的 N 值,即使是两个相邻的值 (k, k+1) 也相距足够远,因此该规则适用。然而,对于增加高于 SQRT(S) 的值,会打开一个窗口,因此该窗口内的任意数量的 N 值将无法有效地相互修剪。这个窗口的大小大约是 K/SQRT(S)。因此,如果 S=10^9,当 K 大约为 10^6 时,此窗口的宽度将接近 30 个数字。这意味着 N[] 可以包含 1 加上从 1000001 到 1000029 的每个数字,修剪优化几乎没有任何好处。
J = S/K
K 是 N 值平均大小的某种度量(Gmean(N) 可能是最好的估计)。如果我们将此应用于蛮力组合搜索,假设修剪无效,我们得到
O((N^(S/Gmean(N)))^(1/Gmean(N)))
,我认为也是
O(N^(S/(Gmean(N)^2)))
.
关于algorithm - 内存受限的硬币可更改多达 10 亿的数字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45060677/
我是一名优秀的程序员,十分优秀!