问题: 查找从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();
答案 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:我不确定,但我想如果我把数字的第一个数字而不是最后分开,也许我可以实现像原始算法一样的解决方案。