这是我的代码,但速度慢,任何方式都可以更快地完成.. 我打的数字范围是123456789但我不能低于15秒,我需要它才能低于5秒..
long num = 0;
for (long i = 0; i <= n; i++)
{
num = num + GetSumOfDigits(i);
}
static long GetSumOfDigits(long n)
{
long num2 = 0;
long num3 = n;
long r = 0;
while (num3 != 0)
{
r = num3 % 10;
num3 = num3 / 10;
num2 = num2 + r;
}
return num2;
}
sum =(n(n+1))/2
没有给我我需要的结果,也没有正确计算..
对于N = 12,和为1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(1 + 0)+(1 + 1)+(1 + 2)= 51。 我需要用公式而不是循环来做这件事。
我在6秒钟内完成了大约15项测试。
并行我从15秒到4-8秒进行了一次测试..
只是为了向你展示我正在做的测试,这很难......
[Test]
public void When123456789_Then4366712385()
{
Assert.AreEqual(4366712385, TwistedSum.Solution(123456789));
}
在我的电脑上,我可以在5秒内完成所有测试。 看看照片..
使用DineMartine
答案我得到了这些结果:
答案 0 :(得分:5)
有点令人费解,但却把时间缩短到实际为零:
private static long getSumOfSumOfDigitsBelow(long num)
{
if (num == 0)
return 0;
// 1 -> 1 ; 12 -> 10; 123 -> 100; 321 -> 100, ...
int pow10 = (int)Math.Pow(10, Math.Floor(Math.Log10(num)));
long firstDigit = num / pow10;
long sum = 0;
var sum999 = getSumOfSumOfDigitsBelow(pow10 - 1);
var sumRest = getSumOfSumOfDigitsBelow(num % pow10);
sum += (firstDigit - 1)*(firstDigit - 0)/2*pow10 + firstDigit*sum999;
sum += firstDigit*(num%pow10 + 1) + sumRest;
return sum;
}
getSumOfSumOfDigitsBelow(123456789) -> 4366712385 (80us)
getSumOfSumOfDigitsBelow(9223372036854775807) -> 6885105964130132360 (500us - unverified)
诀窍是避免一次又一次地计算相同的答案。例如33:
你的方法:
sum = 1+2+3+4+5+6+7+8+9+(1+0)+(1+1)+(1+2)+ ... +(3+2)+(3+3)
我的方法:
sum = 10*(0 + (1+2+3+4+5+6+7+8+9)) +
10*(1 + (1+2+3+4+5+6+7+8+9)) +
10*(2 + (1+2+3+4+5+6+7+8+9)) +
3*(3 + (1 + 2 + 3))
(1+2+3+4+5+6+7+8+9)
- 部分只需计算一次。 0..firstDigit-1
- 技巧可以避免n(n-1)/2
的循环。我希望这是有道理的。
复杂度为O(2^N)
,N计算位数。这看起来非常糟糕,但即使是长期最大,也足够快到5秒的阈值。通过仅调用O(n)
一次,可以将此算法转换为getSumOfSumOfDigitsBelow()
中运行的内容,但看起来会更复杂。
优化的第一步:查看您的算法;)
在DineMartine回答后回到这个问题:
为了进一步优化算法,可以用显式公式替换sum999
- 部分。让我们在代码中加入一些9999...9=10^k-1
并相应地替换:
sum(10^k-1) = (9 - 1)*(9 - 0)/2*pow10 + 9*sum999 + 9*(num%pow10 + 1) + sumRest
sum(10^k-1) = 36*pow10 + 9*sum999 + 9*(num%pow10 + 1) + sumRest
对于sum999
类型的数字, sumRest
和10^k
相同:
sum(10^k-1) = 36*pow10 + 10*sum(10^(k-1)-1) + 9*(num%pow10 + 1)
sum(10^k-1) = 36*pow10 + 10*sum(10^(k-1)-1) + 9*((10^k-1)%pow10 + 1)
sum(10^k-1) = 36*pow10 + 10*sum(10^(k-1)-1) + 9*pow10
sum(10^k-1) = 45*pow10 + 10*sum(10^(k-1)-1)
我们有sum(10^k-1)
的定义,知道sum(9)=45
。我们得到:
sum(10^k-1) = 45*k*10^k
更新的代码:
private static long getSumOfSumOfDigitsBelow(long num)
{
if (num == 0)
return 0;
long N = (int) Math.Floor(Math.Log10(num));
int pow10 = (int)Math.Pow(10, N);
long firstDigit = num / pow10;
long sum = (firstDigit - 1)*firstDigit/2*pow10
+ firstDigit* 45 * N * pow10 / 10
+ firstDigit*(num%pow10 + 1)
+ getSumOfSumOfDigitsBelow(num % pow10);
return sum;
}
这是与DineMartine相同的算法,但是以递归方式表示(我比较了两种实现,是的,我确定它是;))。运行时间几乎为零,时间复杂度为O(N)
计算数字或O(long(N))
计算值。
答案 1 :(得分:5)
您的算法复杂度为N log(N)
。我找到了一个复杂log(N)
的更好的算法。我们的想法是迭代数字的位数:
log10(n) = ln(n)/ln(10) = O(log(n)).
该算法的演示涉及大量的组合演算。所以我选择不在这里写。
以下是代码:
public static long Resolve(long input)
{
var n = (long)Math.Log10(input);
var tenPow = (long)Math.Pow(10, n);
var rest = input;
var result = 0L;
for (; n > 0; n--)
{
var dn = rest / tenPow;
rest = rest - dn * tenPow;
tenPow = tenPow / 10;
result += dn * (rest + 1) + dn * 45 * n * tenPow + dn * (dn-1) * tenPow * 5 ;
}
result += rest * (rest + 1) / 2;
return result;
}
现在你可以在几分之一秒内解决问题。
想法是将输入写为数字列表:
假设解决方案由函数f给出,我们正在寻找g在n上的递归表达式:
实际上g可以写成如下:
如果你找到h,问题就会得到解决。
答案 2 :(得分:3)
为了提高性能,您可以从最高数字开始计算总和。
让r=n%10 + 1
。计算最后r个数的总和。
然后我们注意到,如果n
以9
结尾,则总和可以计算为10 * sum(n/10) + (n+1)/10 * 45
。第一项是所有数字的总和,但是最后一项,第二项是最后一位数的总和。
计算总和的函数变为:
static long GetSumDigitFrom1toN(long n)
{
long num2 = 0;
long i;
long r = n%10 + 1;
if (n <= 0)
{
return 0;
}
for (i = 0; i < r; i++)
{
num2 += GetSumOfDigits(n - i);
}
// The magic number 45 is the sum of 1 to 9.
return num2 + 10 * GetSumDigitFrom1toN(n/10 - 1) + (n/10) * 45;
}
试运行:
GetSumDigitFrom1toN(12L): 51
GetSumDigitFrom1toN(123456789L): 4366712385
时间复杂度为O(log n)
。
答案 3 :(得分:3)
如果您的系统中有多个处理器(或核心),则可以通过并行计算来加快速度。
以下代码演示了(这是一个可编辑的控制台应用程序)。
我在我的系统上尝试的输出(4个超线程核心)对于发布版本如下:
x86 version:
Serial took: 00:00:14.6890714
Parallel took: 00:00:03.5324480
Linq took: 00:00:04.4480217
Fast Parallel took: 00:00:01.6371894
x64 version:
Serial took: 00:00:05.1424354
Parallel took: 00:00:00.9860272
Linq took: 00:00:02.6912356
Fast Parallel took: 00:00:00.4154711
请注意,并行版本的速度提高了约4倍。另请注意,x64版本更快(由于在计算中使用long
)。
代码使用Parallel.ForEach
和Partitioner
将数字范围划分为可用处理器数量的合理区域。它还使用Interlocked.Add()
通过有效锁定快速添加数字。
我还添加了另一种方法,您需要预先计算0到1000之间数字的总和。您应该只需要为程序的每次运行预先计算一次总和。请参阅FastGetSumOfDigits()
。
使用FastGetSumOfDigits()
可将PC上一次最快的时间翻倍。您可以将SUMS_SIZE
的值增加到10的较大倍数,以进一步提高速度,但会占用空间。在我的PC上将它增加到10000会将时间减少到~0.3s
(sums
数组只需要是short
数组,以节省空间。它不需要更大的类型。)
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
internal class Program
{
public static void Main()
{
long n = 123456789;
Stopwatch sw = Stopwatch.StartNew();
long num = 0;
for (long i = 0; i <= n; i++)
num = num + GetSumOfDigits(i);
Console.WriteLine("Serial took: " + sw.Elapsed);
Console.WriteLine(num);
sw.Restart();
num = 0;
var rangePartitioner = Partitioner.Create(0, n + 1);
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
long subtotal = 0;
for (long i = range.Item1; i < range.Item2; i++)
subtotal += GetSumOfDigits(i);
Interlocked.Add(ref num, subtotal);
});
Console.WriteLine("Parallel took: " + sw.Elapsed);
Console.WriteLine(num);
sw.Restart();
num = Enumerable.Range(1, 123456789).AsParallel().Select(i => GetSumOfDigits(i)).Sum();
Console.WriteLine("Linq took: " + sw.Elapsed);
Console.WriteLine(num);
sw.Restart();
initSums();
num = 0;
Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
long subtotal = 0;
for (long i = range.Item1; i < range.Item2; i++)
subtotal += FastGetSumOfDigits(i);
Interlocked.Add(ref num, subtotal);
});
Console.WriteLine("Fast Parallel took: " + sw.Elapsed);
Console.WriteLine(num);
}
private static void initSums()
{
for (int i = 0; i < SUMS_SIZE; ++i)
sums[i] = (short)GetSumOfDigits(i);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long GetSumOfDigits(long n)
{
long sum = 0;
while (n != 0)
{
sum += n%10;
n /= 10;
}
return sum;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static long FastGetSumOfDigits(long n)
{
long sum = 0;
while (n != 0)
{
sum += sums[n % SUMS_SIZE];
n /= SUMS_SIZE;
}
return sum;
}
static short[] sums = new short[SUMS_SIZE];
private const int SUMS_SIZE = 1000;
}
}
答案 4 :(得分:2)
0..99999999的位数总和为10000000 * 8 *(0 + 1 + 2 + ... + 9)。 然后使用循环计算剩余部分(100000000..123456789)可能足够快。
对于N = 12: 0..9的数字总和为1 * 1 * 45.然后使用你的循环10,11,12。
对于N = 123: 0..99的数字总和为10 * 2 * 45.然后使用你的循环为100..123。
你看到了模式吗?
答案 5 :(得分:0)
您可以尝试不同的方法:
Char
数组示例代码:
long num = 123456789;
var numChars = num.ToString().ToCharArray();
var zeroCode = Convert.ToByte('0');
var sum = numChars.Sum(ch => Convert.ToByte(ch) - zeroCode);