给定一个大数,例如9223372036854775807
( Int64.MaxValue
),求和的最快方法是什么?
目前我正在 ToStringing 并将每个字符重新解析为int
:
num.ToString().Sum(c => int.Parse(new String(new char[] { c })));
这肯定是非常低效的。有什么建议么?
最后,您将如何使用BigInteger
?
谢谢
好吧,另一种选择是:
int sum = 0;
while (value != 0)
{
int remainder;
value = Math.DivRem(value, 10, out remainder);
sum += remainder;
}
BigInteger
也有一个DivRem
方法,所以你可以使用相同的方法。
请注意,我发现DivRem
它不如“手动”执行相同的算术那么快,所以如果您真的对速度感兴趣,您可能需要考虑这一点。
还要考虑一个带有(比如)1000 个元素的查找表,其中预先计算了总和:
int sum = 0;
while (value != 0)
{
int remainder;
value = Math.DivRem(value, 1000, out remainder);
sum += lookupTable[remainder];
}
这意味着更少的迭代,但每次迭代都有一个额外的数组访问......
没有人讨论过 BigInteger 版本。为此,我会查看 10 1、 10 2、 10 4、 10 8等等,直到你找到最后一个小于你的值的 10 2 n 。取您的数字 div 和 mod 10 2 n来得出 2 个较小的值。清洗、漂洗并递归地重复。(您应该将 10 的迭代平方保留在一个数组中,并在递归部分传递有关下一个要使用的幂的信息。)
对于具有 k 位的 BigInteger,除以 10 是 O(k)。因此,用朴素算法求数字的总和是 O(k 2 )。
我不知道 C# 在内部使用什么,但是用于将 k 位乘以或除以 k 位整数的非朴素算法都可以在时间 O(k 1.6 ) 或更好的时间内工作(大多数要好得多,但有一个开销使它们对于“小大整数”更糟)。在这种情况下,准备您的初始权力列表并拆分一次需要时间 O(k 1.6 )。这为您提供了 2 个大小为 O((k/2) 1.6 ) = 2 -0.6 O(k 1.6 ) 的问题。在下一个级别,您有 4 个大小为 O((k/4) 1.6 ) 的问题需要另外 2 个-1.2 O(k 1.6 ) 工作。将所有项加起来,2 的幂变成一个收敛到常数的几何级数,所以总功为 O(k1.6 )。
这是一个明确的胜利,如果你使用数千位数的数字,胜利将非常非常明显。
是的,它可能有点效率低下。我可能只是反复除以 10,每次将余数加在一起。
性能优化的第一条规则:可以乘法时不要除法。以下函数将采用四位数字 0-9999 并按照您的要求进行操作。中间计算大于 16 位。我们将该数字乘以 1/10000,并将结果作为 Q16 定点数。然后通过乘以 10 并取整数部分来提取数字。
#define TEN_OVER_10000 ((1<<25)/1000 +1) // .001 Q25
int sum_digits(unsigned int n)
{
int c;
int sum = 0;
n = (n * TEN_OVER_10000)>>9; // n*10/10000 Q16
for (c=0;c<4;c++)
{
printf("Digit: %d\n", n>>16);
sum += n>>16;
n = (n & 0xffff) * 10; // next digit
}
return sum;
}
这可以扩展到更大的尺寸,但它很棘手。您需要确保定点计算中的舍入始终正常工作。我还做了 4 位数字,所以定点乘法的中间结果不会溢出。
Int64 BigNumber = 9223372036854775807;
String BigNumberStr = BigNumber.ToString();
int Sum = 0;
foreach (Char c in BigNumberStr)
Sum += (byte)c;
// 48 is ascii value of zero
// remove in one step rather than in the loop
Sum -= 48 * BigNumberStr.Length;
而不是 int.parse,为什么不从每个数字中减去“0”来获得实际值。
请记住,'9' - '0' = 9,因此您应该能够按 k 顺序(数字的长度)执行此操作。减法只是一种操作,因此不应减慢速度。