让 A 表示正整数的集合,其十进制表示不包含数字0. 中元素的倒数之和已知 为23.10345。
实施例。 1,2,3,4,5,6,7,8,9,11-19,21-29,31-39,41-49,51-59,61-69,71-79,81-89, 91-99,111-119,......
然后取每个数字的倒数,并总计总和。
如何以数字方式验证?
编写计算机程序以验证此编号。
以下是我到目前为止所写的内容,我需要帮助限制这个问题,因为这需要很长时间才能完成:
Java中的代码
import java.util.*;
public class recip
{
public static void main(String[] args)
{
int current = 0; double total = 0;
while(total < 23.10245)
{
if(Integer.toString(current).contains("0"))
{
current++;
}
else
{
total = total + (1/(double)current);
current++;
}
System.out.println("Total: " + total);
}
}
}
答案 0 :(得分:8)
正确接近时,这并不难。
例如,假设您要查找以123开始并以k非零数字结尾的所有整数的倒数之和(即最左边的数字)。显然有9个 k 这样的整数,这些整数的倒数在1 /(124 * 10 k )范围内.1 /(123 * 10 < SUP>ķ)。因此,所有这些整数的倒数之和受(9/10) k / 124和(9/10) k / 123的限制。
为了找到以123开始的所有倒数之和的界限,必须将每个k> = 0的上限加起来。这是一个几何系列,因此可以推导出以123开头的整数倒数之和由10 *(9/10) k / 124和10 *(9/10)<界定SUP>ķ / 123。
同样的方法当然可以应用于最左边数字的任何组合。 我们在左侧检查的位数越多,结果就越准确。 以下是python中这种方法的实现:
def approx(t,k):
"""Returns a lower bound and an upper bound on the sum of reciprocals of
positive integers starting with t not containing 0 in its decimal
representation.
k is the recursion depth of the search, i.e. we append k more digits
to t, before approximating the sum. A larger k gives more accurate
results, but takes longer."""
if k == 0:
return 10.0/(t+1), 10.0/t
else:
if t > 0:
low, up = 1.0/t, 1.0/t
else:
low, up = 0, 0
for i in range(10*t+1, 10*t+10):
l,u = approx(i, k-1)
low += l
up += u
return low, up
例如,调用约(0,8)给出下限和上限: 23.103447707 ......和23.103448107 .... 这与OP提出的索赔23.10345很接近。
有些方法可以更快地收敛到有问题的总和,但它们需要更多的数学运算。 可以找到更好的近似值here。问题的一般化是Kempner series。
答案 1 :(得分:1)
对于current
大于某个阈值N
的所有值,1.0/(double)current
将足够小,total
不会因添加1.0/(double)current
而增加。因此,终止标准应该类似于
while(total != total + (1.0/(double)current))
而不是针对已知先验的限制进行测试。当current
达到N
的特殊值时,您的循环将停止。
答案 2 :(得分:1)
我怀疑转换为字符串然后检查字符'0'是一个耗时太长的步骤。如果你想避免全部为零,那么可能有助于增加current
:
(编辑 - 感谢Aaron McSmooth)
current++;
for( int i = 10000000; i >= 10; i = i / 10 )
{
if ( current % i ) == 0
{
current = current + ( i / 10 );
}
}
这是未经测试的,但概念应该是明确的:每当你达到10的幂的倍数(例如300或20000)时,你添加下一个10的较低功率(在我们的示例10 + 1和1000 + 100中)分别为+ 10 + 1),直到你的号码中没有更多的零。
相应地更改while
循环,看看如果您的问题变得易于管理,这是否有助于提高性能。
哦,您可能也希望稍微限制System.out
输出。每十分之一,一千分之一或第10000次迭代是否足够?
编辑第二个:
经过一段时间的睡眠,我怀疑我的回答可能有点短视(如果你愿意的话,可以归咎于迟到的时候)。我只是希望,哦,一百万次current
次迭代会让你找到解决方案并将其留在那里,而不是使用log( current )
等来计算修正案例。
第二个想法,我发现整个问题存在两个问题。一个是你的目标数量23.10345是一个leeeeettle圆我的口味。毕竟,你正在添加数千个项目,如“1/17”,“1/11111”等,带有无限的十进制表示,并且它们极不可能恰好相加到23.10345。如果一些数学数学专家这么说,那很好 - 但是我想看看他们得出这个结论的算法。
另一个问题与第一个问题有关,并且涉及有理数的有限内存二进制表示。您可以使用BigDecimals,但我有疑虑。
所以,基本上,我建议你重新编程数值算法,而不是去蛮力解决方案。遗憾。
编辑第三个: 出于好奇,我用C ++编写了这个来测试我的理论。它现在运行了6分钟,大约是14.5(大约550万次迭代)。我们会看到。
当前版本是
double total = 0;
long long current = 0, currPowerCeiling = 10, iteration = 0;
while( total < 23.01245 )
{
current++;
iteration++;
if( current >= currPowerCeiling )
currPowerCeiling *= 10;
for( long long power = currPowerCeiling; power >= 10; power = power / 10 )
{
if( ( current % power ) == 0 )
{
current = current + ( power / 10 );
}
}
total += ( 1.0 / current );
if( ! ( iteration % 1000000 ) )
std::cout << iteration / 1000000 << " Mio iterations: " << current << "\t -> " << total << std::endl;
}
std::cout << current << "\t" << total << std::endl;
手动计算currPowerCeiling
(或者可能会调用此方法)可以在每次迭代时保存一些log10
和pow
计算。每一点点都有帮助 - 但它仍然需要永远......
编辑第四个: 状态大约为66,000 mio迭代,总计高达16.2583,运行时间大约为13小时。看起来不太好,Bobby S. - 我建议采用更多的数学方法。
答案 3 :(得分:1)
如何将当前数字存储为每个数组元素为0-9的字节数组?这样,您可以非常快速地检测零(使用==
而不是String.contains
比较字节)。
缺点是你需要自己实现增量而不是使用++
。您还需要设计一种方法来标记“不存在”的数字,这样您就不会将它们检测为零。为不存在的数字存储-1
听起来像是一个合理的解决方案。
答案 4 :(得分:0)
public class SumOfReciprocalWithoutZero {
public static void main(String[] args) {
int maxSize=Integer.MAX_VALUE/10;
long time=-System.currentTimeMillis();
BitSet b=new BitSet(maxSize);
setNumbersWithZeros(10,maxSize,b);
double sum=0.0;
for(int i=1;i<maxSize;i++)
{
if(!b.get(i))
{
sum+=1.0d/(double)i;
}
}
time+=System.currentTimeMillis();
System.out.println("Total: "+sum+"\nTimeTaken : "+time+" ms");
}
static void setNumbersWithZeros(int srt,int end,BitSet b)
{
for(int j=srt;j<end;j*=10)
{
for(int i=1;i<=10;i++)
{
int num=j*i;
b.set(num);
}
if(j>=100)
setInbetween(j, b);
}
}
static void setInbetween(int strt,BitSet b)
{
int bitToSet;
bitToSet=strt;
for(int i=1;i<=10;i++)
{
int nxtInt=-1;
while((nxtInt=b.nextSetBit(nxtInt+1))!=strt)
{
b.set(bitToSet+nxtInt);
}
nxtInt=-1;
int lim=strt/10;
while((nxtInt=b.nextClearBit(nxtInt+1))<lim)
{
b.set(bitToSet+nxtInt);
}
bitToSet=strt*i;
}
}
}
这是一个使用BitSet的实现。我计算了范围(1-Integer.MAX_VALUE/10)
中所有整数的倒数之和。总和达到13.722766931560747
。这是我可以使用BitSet计算的最大值,因为最大范围因为BitSet是Integer.MAX_VALUE。我需要将它除以10并限制范围以避免溢出。但速度有显着提高。我只是发布这个代码以防它可能会给你一些新的想法来改善你的代码。(使用VM参数-Xmx[Size>350]m
增加内存)
<强>输出:强>
Total: 13.722766931560747
TimeTaken : 60382 ms
<强>更新强>
Java移植上一个已删除的答案:
public static void main(String[] args) {
long current =11;
double tot=1 + 1.0/2 + 1.0/3 + 1.0/4 + 1.0/5 + 1.0/6 + 1.0/7 + 1.0/8 + 1.0/9;
long i=0;
while(true)
{
current=next_current(current);
if(i%10000!=0)
System.out.println(i+" "+current+" "+tot);
for(int j=0;j<9;j++)
{
tot+=(1.0/current + 1.0/(current + 1) + 1.0/(current + 2) + 1.0/(current + 3) + 1.0/(current + 4) +
1.0/(current + 5) + 1.0/(current + 6) + 1.0/(current + 7) + 1.0/(current + 8));
current += 10;
}
i++;
}
}
static long next_current(long n){
long m=(long)Math.pow(10,(int)Math.log10(n));
boolean found_zero=false;
while(m>=1)
{
if(found_zero)
n+=m;
else if((n/m)%10==0)
{
n=n-(n%m)+m;
found_zero=true;
}
m=m/10;
}
return n;
}
答案 5 :(得分:0)
对于带符号的32位整数,该程序永远不会停止。它实际上会收敛到-2097156
。由于带符号的32位整数的最大谐波数(从1到N的积分倒数之和)为~14.66
,因此该循环将永远不会终止,即使当前包裹从2^31 - 1
到{{ 1}}。由于最大负32位整数的倒数为〜-6.46666e-10,每次电流返回-2^31
时,总和将为负。鉴于0
可表示的最大数字double
为number + + 1/2^31 == number
/ 2^52
,您获得的汇总值大约为2^31
。
话虽如此,假设您没有直接计算任意整数的谐波数的方法,您可以采取一些措施来加速内循环。首先,最昂贵的操作将是-2097156
;必须与控制台交互,在这种情况下,程序最终必须将缓冲区刷新到控制台(如果有的话)。有些情况下可能实际上不会发生,但由于您使用它进行调试,因此与此问题无关。
但是,您还要花很多时间来确定数字是否为零。您可以翻转该测试以生成整数范围,以便在该范围内保证不具有零数字的整数。这是非常简单的逐步增加(在C ++中,但足以转换为Java):
System.out.println
上面的代码是迭代每个数字范围,使得范围只包含没有零的数字。它的工作原理是确定如何从N000 ...到N111 ...以及从N111 ...到(N + 1)000 ...,携带(N + 1)到1(0)000 ...如果必要的。
在我的笔记本电脑上,我可以在8.73226秒内生成2 ^ 31 - 1的谐波数。