项目欧拉问题14:
以下迭代序列是 为积极的一套定义 整数:
n→n / 2(n为偶数)n→3n + 1(n为 奇)
使用上面的规则并从中开始 13,我们生成以下内容 顺序:13→40→20→10→5→16→ 8→4→2→1
可以看出这个序列 (从13开始到1完成) 包含10个术语。虽然没有 它已被证实(Collatz问题) 被认为是所有的起始数字 结束于1。
哪个起始编号,一个 百万,产生最长的链?
我的第一直觉是创建一个计算链的函数,并使用1到100万之间的每个数运行它。显然,这需要长时间。根据Project Euler的“关于”页面,比解决这个应该采取的方式更长。我在Project Euler上发现了一些涉及大量数字的问题,这些问题导致一个运行数小时的程序没有完成。显然,我做错了什么。
如何快速处理大量数字?
我在这里缺少什么?
答案 0 :(得分:4)
阅读memoization。关键的见解是,如果你有一个序列从A开始,其长度为1001,然后你得到一个产生A的序列B,你就不要再重复所有这些了。</ p>
答案 1 :(得分:3)
这是Mathematica中的代码,使用memoization和recursion。只有四行:)
f[x_] := f[x] = If[x == 1, 1, 1 + f[If[EvenQ[x], x/2, (3 x + 1)]]];
Block[{$RecursionLimit = 1000, a = 0, j},
Do[If[a < f[i], a = f[i]; j = i], {i, Reverse@Range@10^6}];
Print@a; Print[j];
]
输出....链长'525',数字是......哦......字体太小了! :)
顺便说一句,在这里你可以看到每个链长的频率图
答案 2 :(得分:2)
从1,000,000开始,生成链。跟踪链中生成的每个数字,因为您确定它们的链小于起始数字的链。达到1后,将起始编号及其链长存储起来。获取之前未生成的下一个最大数字,然后重复此过程。
这将为您提供数字和链长的列表。采取最大的链长,这是你的答案。
我会做一些代码来澄清。
public static long nextInChain(long n) {
if (n==1) return 1;
if (n%2==0) {
return n/2;
} else {
return (3 * n) + 1;
}
}
public static void main(String[] args) {
long iniTime=System.currentTimeMillis();
HashSet<Long> numbers=new HashSet<Long>();
HashMap<Long,Long> lenghts=new HashMap<Long, Long>();
long currentTry=1000000l;
int i=0;
do {
doTry(currentTry,numbers, lenghts);
currentTry=findNext(currentTry,numbers);
i++;
} while (currentTry!=0);
Set<Long> longs = lenghts.keySet();
long max=0;
long key=0;
for (Long aLong : longs) {
if (max < lenghts.get(aLong)) {
key = aLong;
max = lenghts.get(aLong);
}
}
System.out.println("number = " + key);
System.out.println("chain lenght = " + max);
System.out.println("Elapsed = " + ((System.currentTimeMillis()-iniTime)/1000));
}
private static long findNext(long currentTry, HashSet<Long> numbers) {
for(currentTry=currentTry-1;currentTry>=0;currentTry--) {
if (!numbers.contains(currentTry)) return currentTry;
}
return 0;
}
private static void doTry(Long tryNumber,HashSet<Long> numbers, HashMap<Long, Long> lenghts) {
long i=1;
long n=tryNumber;
do {
numbers.add(n);
n=nextInChain(n);
i++;
} while (n!=1);
lenghts.put(tryNumber,i);
}
答案 3 :(得分:2)
假设您有一个函数CalcDistance(i)
来计算1
的“距离”。例如,CalcDistance(1) == 0
和CalcDistance(13) == 9
。这是一个天真的递归实现这个函数(在C#中):
public static int CalcDistance(long i)
{
if (i == 1)
return 0;
return (i % 2 == 0) ? CalcDistance(i / 2) + 1 : CalcDistance(3 * i + 1) + 1;
}
问题是这个函数必须一遍又一遍地计算多个数字的距离。通过给它一个记忆,你可以让它变得更聪明(并且更快)。例如,让我们创建一个静态数组,可以存储第一百万个数字的距离:
static int[] list = new int[1000000];
我们使用-1
预填充列表中的每个值,以指示尚未计算该位置的值。在此之后,我们可以优化CalcDistance()
函数:
public static int CalcDistance(long i)
{
if (i == 1)
return 0;
if (i >= 1000000)
return (i % 2 == 0) ? CalcDistance(i / 2) + 1 : CalcDistance(3 * i + 1) + 1;
if (list[i] == -1)
list[i] = (i % 2 == 0) ? CalcDistance(i / 2) + 1: CalcDistance(3 * i + 1) + 1;
return list[i];
}
如果i >= 1000000
,那么我们就无法使用我们的列表,因此我们必须始终对其进行计算。如果i < 1000000
,那么我们检查该值是否在列表中。如果没有,我们先计算它并将其存储在列表中。否则我们只返回列表中的值。使用此代码,处理所有数百万个数字大约需要120毫秒。
这是一个非常简单的memoization示例。我在这个例子中使用一个简单的列表来存储中间值。您可以在适当的时候使用更高级的数据结构,如哈希表,向量或图形。
答案 4 :(得分:0)
最小化循环的深度级别,并使用IList或IDictionary等高效数据结构,可以在需要扩展时自动调整自身大小。如果使用普通数组,则需要在扩展时将它们复制到更大的数组中 - 效率不高。
答案 5 :(得分:0)
此变体不使用HashMap,但仅尝试不重复前1000000个数字。我没有使用散列图,因为找到的最大数字大约是56亿,哈希映射可能会崩溃。
我已经完成了一些premature optimization
。我使用/
代替>>
,而不是%
而不是&
。而不是*
我使用了一些+
。
void Main()
{
var elements = new bool[1000000];
int longestStart = -1;
int longestRun = -1;
long biggest = 0;
for (int i = elements.Length - 1; i >= 1; i--) {
if (elements[i]) {
continue;
}
elements[i] = true;
int currentStart = i;
int currentRun = 1;
long current = i;
while (current != 1) {
if (current > biggest) {
biggest = current;
}
if ((current & 1) == 0) {
current = current >> 1;
} else {
current = current + current + current + 1;
}
currentRun++;
if (current < elements.Length) {
elements[current] = true;
}
}
if (currentRun > longestRun) {
longestStart = i;
longestRun = currentRun;
}
}
Console.WriteLine("Longest Start: {0}, Run {1}", longestStart, longestRun);
Console.WriteLine("Biggest number: {0}", biggest);
}