找到从1到N的所有数字的数字之和

时间:2016-02-25 20:48:27

标签: algorithm math big-o

问题: 查找从1到N的所有数字的数字总和(包括两端)

时间复杂度应为O(logN)

对于N = 10,总和为1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(1 + 0)= 46

对于N = 11,总和为1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(1 + 0)+(1 + 1)= 48

对于N = 12,总和为1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +(1 + 0)+(1 + 1)+(1 + 2)= 51

这种递归solution就像一个魅力,但我想了解达到这种解决方案的理由。我相信它基于有限感应,但有人可以准确地说明如何解决这个问题吗?

我已粘贴(稍作修改)上述解决方案:

static long Solution(long n)
{
    if (n <= 0)
        return 0;
    if (n < 10)
        return (n * (n + 1)) / 2; // sum of arithmetic progression
    long x = long.Parse(n.ToString().Substring(0, 1)); // first digit
    long y = long.Parse(n.ToString().Substring(1)); // remaining digits
    int power = (int)Math.Pow(10, n.ToString().Length - 1);

    // how to reach this recursive solution?
    return (power * Solution(x - 1))
    + (x * (y + 1)) 
    + (x * Solution(power - 1)) 
    + Solution(y);
}

单元测试(非O(logN)):

    long count = 0;
    for (int i=1; i<=N; i++)
    {
        foreach (var c in i.ToString().ToCharArray())
            count += int.Parse(c.ToString());
    }

或者:

Enumerable.Range(1, N).SelectMany(
    n => n.ToString().ToCharArray().Select(
        c => int.Parse(c.ToString())
    )
).Sum();

2 个答案:

答案 0 :(得分:2)

这实际上是O(n^log10(2)) - 时间解决方案(log10(2)约为0.3)。不确定是否重要。我们有n = xy,其中我使用连接来表示连接,而不是乘法。以下是四条关键词,下面有评论。

return (power * Solution(x - 1))

这会计算x地点对1个包含的x*power个数的贡献。这种递归调用对复杂性没有贡献,因为它以恒定的时间返回。

+ (x * (y + 1))

这会计算x地点对x*power个包含的数字n的贡献。

+ (x * Solution(power - 1))

这会计算从1包含到x*power排除的数字的较低位置的贡献。此通话的号码比n短一号。

+ Solution(y);

这会计算从x*power包含到n的数字的较低位置的贡献。此通话的号码比n短一号。

我们得到应用主定理案例1的时间限制。要将运行时间降至O(log n),我们可以分析计算Solution(power - 1)。我不记得关闭形式是什么。

答案 1 :(得分:0)

在思考了一段时间(并找到similar answers)后,我想我可以达到给我另一种解决方案的理由。

<强>解释
设S(n)为所有数字0&lt; = k&lt;的数字之和。 ñ。
设D(k)只是k的普通数字和 (我将省略括号&gt;清晰度,所以考虑Dx = D(x)

如果n> = 10,让通过分割最后一位和十位(n = 10 * k + r)(k,r为整数)

我们需要求和S(n)= S(10 * k + r)= S(10 * k)+ D(10 * k + 1)+ ... + D(10 * k + r)< / p>

第一部分S(10 * k)遵循以下模式:
S(10 * 1)= D1 + D2 + D3 + ... + D9 =(1 + 2 + 3 + ... + 9)* 1 + D10
S(10 * 2)= D1 + D2 + D3 + ... + D19 =(1 + 2 + 3 + ... + 9)* 2 + 1 * 9 + D10 + D20
S(10 * 3)= D1 + D2 + D3 + ... + D29 =(1 + 2 + 3 + ... + 9)* 3 + 1 * 9 + 2 * 9 + D10 + ... + D20 + D30

所以 S(10 * k) =(1 + 2 + 3 + ... + 9)* k + 9 * S(k-1)+ S(k-1)+ D(10 * k)= 45 * k + 10 * S(k-1)+ D(10 * k)

关于最后一部分,我们知道D(10 * k + x)= D(10 * k)+ D(x)= D(k)+ x,所以最后一部分可以简化:

D(10 * k + 1)+ ... + D(10 * k + r)= D(k)+1 + D(k)+2 + ... D(k)+ r = r D(k)+(1 + 2 + ... + r)= r D(k)+ r *(1 + r)/ 2 < /强>

因此,添加等式的两个部分(和分组D(k))我们有:
S(n)= 45 * k + 10 * S(k-1)+(1 + r)D(k)+ r *(1 + r)/ 2

替换k和r我们有:
S(n)= 45 * k + 10 * S((n / 10)-1)+(1 + n%10) D(n / 10)+ n%10 ( 1 + N%10)/ 2

<强>伪代码

S(n):
  if n=0, sum=0
  if n<10, n*(1+n)/2
  r=n%10 # let's decompose n = 10*k + r (being k, r integers). 
  k=n/10
  return 45*k + 10*S((n/10)-1) + (1+n%10)*D(n/10) + n%10*(1+n%10)/2
D(n):
  just sum digits

C#中的第一个算法(来自原始问题的算法)

static BigInteger Solution(BigInteger n)
{
    if (n <= 0)
        return 0;
    if (n < 10)
        return (n * (n + 1)) / 2; // sum of arithmetic progression
    long x = long.Parse(n.ToString().Substring(0, 1)); // first digit
    long y = long.Parse(n.ToString().Substring(1)); // remaining digits
    BigInteger power = BigInteger.Pow(10, n.ToString().Length - 1);
    var log = Math.Round(BigInteger.Log10(power)); // BigInteger.Log10 can give rounding errors like 2.99999

    return (power * Solution(x - 1)) //This counts the contribution of the x place for the numbers from 1 inclusive to x*power exclusive. This recursive call doesn't contribute to the complexity because it returns in constant time.
    + (x * (y + 1)) //This counts the contribution of the x place for the numbers from x*power inclusive to n inclusive.
    //+ (x * Solution(power - 1)) // This counts the contribution of the lower-order places for the numbers from 1 inclusive to x*power exclusive. This call is on a number one digit shorter than n.
    + (x * 45*new BigInteger(log)* BigInteger.Pow(10,(int)log-1)) // 
    + Solution(y);
}

C#

中的第二个算法(从上面的公式中推导出来)
static BigInteger Solution2(BigInteger n)
{
    if (n <= 0)
        return 0;
    if (n < 10)
        return (n * (n + 1)) / 2; // sum of arithmetic progression
    BigInteger r = BigInteger.ModPow(n, 1, 10); // decompose n = 10*k + r
    BigInteger k = BigInteger.Divide(n, 10);
    return 45 * k
         + 10*Solution2(k-1)                      // 10*S((n/10)-1)
         + (1+r) * (k.ToString().ToCharArray().Select(x => int.Parse(x.ToString())).Sum()) //  (1+n%10)*D(n/10)
         + (r * (r + 1)) / 2;                     //n%10*(1+n%10)/2
}

编辑:根据我的测试,它的运行速度比原始版本(使用递归两次)快,并且修改版本以计算解决方案(电源 - 1)只需一步。

PS:我不确定,但我想如果我把数字的第一个数字而不是最后分开,也许我可以实现像原始算法一样的解决方案。