答案 0 :(得分:10)
这样的问题有一个明确的数学解决方案。让我们假设这个值是零填充到最大位数(它不是,但我们稍后会补偿),并通过它推理:
任何给定数字的明显模式,如果范围从0到10的幂,则 N * 10 N-1 ,其中 N 是10的力量。
如果范围不是10的幂,该怎么办?以10的最低功率开始,然后进行操作。最简单的处理方法是最大值399.我们知道,对于100的每个倍数,每个数字出现至少 20次,但我们必须补偿它出现的次数。最高位数的位置,对于数字0-3将精确为100,对于所有其他数字则为零。具体而言,相关数字的额外增加量为10 N 。
将其置于公式中,对于比10的幂的某个倍数(即399,6999等)小1的上限,它变为: M * N * 10 N-1 + iif(d <= M,10 N ,0)
现在你只需要处理其余部分(我们称之为 R )。以445为例。这是399的结果,加上400-445的范围。在此范围内,MSD出现 R 更多次,并且所有数字(包括MSD)也出现在与范围[0 - R ]相同的频率上。< / p>
现在我们只需要补偿领先的零。这种模式很简单 - 只是:
10 N + 10 N-1 + 10 N-2 + ... + ** 10 0 强>
更新:此版本正确考虑了“填充零”,即处理余数时中间位置的零([4 0 0,4 < 0 1,4 0 2,...])。找出填充零有点难看,但修改后的代码(C风格的伪代码)处理它:
function countdigits(int d, int low, int high) {
return countdigits(d, low, high, false);
}
function countdigits(int d, int low, int high, bool inner) {
if (high == 0)
return (d == 0) ? 1 : 0;
if (low > 0)
return countdigits(d, 0, high) - countdigits(d, 0, low);
int n = floor(log10(high));
int m = floor((high + 1) / pow(10, n));
int r = high - m * pow(10, n);
return
(max(m, 1) * n * pow(10, n-1)) + // (1)
((d < m) ? pow(10, n) : 0) + // (2)
(((r >= 0) && (n > 0)) ? countdigits(d, 0, r, true) : 0) + // (3)
(((r >= 0) && (d == m)) ? (r + 1) : 0) + // (4)
(((r >= 0) && (d == 0)) ? countpaddingzeros(n, r) : 0) - // (5)
(((d == 0) && !inner) ? countleadingzeros(n) : 0); // (6)
}
function countleadingzeros(int n) {
int tmp= 0;
do{
tmp= pow(10, n)+tmp;
--n;
}while(n>0);
return tmp;
}
function countpaddingzeros(int n, int r) {
return (r + 1) * max(0, n - max(0, floor(log10(r))) - 1);
}
正如你所看到的,它有点丑陋但它仍然在O(log n)时间运行,所以如果你需要处理数十亿的数字,这仍然会给你即时结果。 :-)如果你在[0 - 1000000]范围内运行它,你会获得与高性能标记发布的完全相同的分布,所以我几乎肯定它是正确的。
仅供参考,inner
变量的原因是前导零函数已经递归,因此它只能在第一次执行countdigits
时计算。
更新2:如果代码难以阅读,请参考countdigits
返回语句的每一行的含义(我尝试了内联注释但是它们使代码均匀更难阅读):
答案 1 :(得分:8)
我假设你想要一个数字在一个范围内的解决方案,你有一个起始和结束的数字。想象一下从起始编号开始并计算直到达到结束编号 - 它会起作用,但速度很慢。我认为快速算法的技巧是要意识到为了在10 ^ x位置上升一位并保持其他所有相同,你需要使用它之前的所有数字10 ^ x次加上所有数字0 -9 10 ^(x-1)次。 (除非您的计数可能涉及超过第x位的进位 - 我在下面更正。)
这是一个例子。假设您从523到1004计数。
要加快最后一位,请查看最右边两个位置的部分。它使用每个数字10 + 1次。一般来说,1 + 10 + ... + 10 ^ n =(10 ^(n + 1) - 1)/ 9,我们可以用来加速计数。
我的算法是从开始编号到结束编号计数(使用基数10计数),但使用上面的事实来快速完成。您遍历起始编号的数字从最小到最重要,并在每个地方计数,以便该数字与结束编号中的数字相同。在每个点,n是你到达一个进位之前需要做的向上计数的数量,以及之后你需要做的数量。
现在让我们假设伪代码计为一种语言。那么,这就是我要做的事情:
convert start and end numbers to digit arrays start[] and end[] create an array counts[] with 10 elements which stores the number of copies of each digit that you need iterate through start number from right to left. at the i-th digit, let d be the number of digits you must count up to get from this digit to the i-th digit in the ending number. (i.e. subtract the equivalent digits mod 10) add d * (10^i - 1)/9 to each entry in count. let m be the numerical value of all the digits to the right of this digit, n be 10^i - m. for each digit e from the left of the starting number up to and including the i-th digit, add n to the count for that digit. for j in 1 to d increment the i-th digit by one, including doing any carries for each digit e from the left of the starting number up to and including the i-th digit, add 10^i to the count for that digit for each digit e from the left of the starting number up to and including the i-th digit, add m to the count for that digit. set the i-th digit of the starting number to be the i-th digit of the ending number.
哦,由于i的值每次增加1,跟踪你的旧10 ^ i并将其乘以10得到新值,而不是每次取幂。
答案 2 :(得分:6)
这是一个非常糟糕的答案,我很惭愧发布它。我要求Mathematica计算所有数字中使用的数字,从1到1,000,000,没有前导0。这就是我得到的:
0 488895
1 600001
2 600000
3 600000
4 600000
5 600000
6 600000
7 600000
8 600000
9 600000
下次您在硬件商店中订购粘性数字进行销售时,按这些比例订购,您就不会错。
答案 3 :(得分:5)
我asked this question on Math Overflow,因为问这么简单的问题而被打屁股。其中一位用户对我表示同情,并说如果我把它发布到The Art of Problem Solving,他会回答它;所以我做到了。
以下是他发布的答案:
http://www.artofproblemsolving.com/Forum/viewtopic.php?p=1741600#1741600
令人尴尬的是,我的数学不足以理解他发布的内容(这个家伙已经19岁......这太令人沮丧了)。我真的需要参加一些数学课。
从好的方面来看,这个等式是递归的,所以将它理解为具有几行代码的递归函数应该是一个简单的事情,由了解数学的人。
答案 4 :(得分:5)
要从一个数字中取出数字,我们只需要进行一次代价高昂的字符串转换,如果我们不能做一个mod,数字最快就可以按下这样的数字:
feed=number;
do
{ digit=feed%10;
feed/=10;
//use digit... eg. digitTally[digit]++;
}
while(feed>0)
该循环应该非常快,并且可以放在开始到结束数字的循环内,以便用最简单的方法计算数字。
为了更快,对于更大的数字范围,我正在寻找一种优化的方法来计算从0到数字的所有数字* 10 ^显着性 (从一开始就结束了我)
这是一个表格,显示了一些有效数字的数字。 这些包括0,但不包括最高价值,这是一个疏忽 但它可能更容易看到模式(这里没有最高值数字) 这些标签不包括尾随零,
1 10 100 1000 10000 2 20 30 40 60 90 200 600 2000 6000
0 1 1 10 190 2890 1 2 3 4 6 9 30 110 490 1690
1 0 1 20 300 4000 1 12 13 14 16 19 140 220 1600 2800
2 0 1 20 300 4000 0 2 13 14 16 19 40 220 600 2800
3 0 1 20 300 4000 0 2 3 14 16 19 40 220 600 2800
4 0 1 20 300 4000 0 2 3 4 16 19 40 220 600 2800
5 0 1 20 300 4000 0 2 3 4 16 19 40 220 600 2800
6 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
7 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
8 0 1 20 300 4000 0 2 3 4 6 19 40 120 600 1800
9 0 1 20 300 4000 0 2 3 4 6 9 40 120 600 1800
编辑:清理我的亲戚 想法:
从暴力表中显示出来 从0(包括)到 poweroTen(notinc)可见 一个十大力量的主力:
increments tally[0 to 9] by md*tp*10^(tp-1)
increments tally[1 to md-1] by 10^tp
decrements tally[0] by (10^tp - 10)
(to remove leading 0s if tp>leadingzeros)
can increment tally[moresignificantdigits] by self(md*10^tp)
(to complete an effect)
如果对每个有效数字应用这些计数调整, 计数应该被修改,好像从0到结束1计数
可以反转调整以删除前一个范围(起始编号)
感谢Aaronaught提供完整且经过测试的答案。
答案 5 :(得分:3)
你的方法很好。我不确定你为什么需要比你描述的更快的东西。
或者,这将为您提供即时解决方案:在您真正需要它之前,计算从1到某个最大数量所需的内容。您可以存储每个步骤所需的数字。如果你有一个像你的第二个例子的范围,它将是1到300所需的,减去1到50所需的范围。
现在你有一个可以随意调用的查找表。多达10,000只需要几MB,一次只需几分钟计算一次?
答案 6 :(得分:3)
我知道这个问题有一个公认的答案,但我的任务是为求职面试编写这个代码,我想我想出了一个快速的替代解决方案,不需要循环,可以根据需要使用或丢弃前导零。
事实上这很简单,但不容易解释。
如果列出前n个数字
1
2
3
.
.
.
9
10
11
通常以从左到右的方式开始计算从开始房间号码到结束房间号码所需的数字,因此对于上面我们有一个1,一个2,一个3 ...一个9,两个1个零,4个1等。我见过的大多数解决方案都采用了这种方法进行了一些优化来加快速度。
我所做的是在列中垂直计数,如数百,数十和单位。你知道最高的房间号,所以我们可以通过一个分区来计算数百列中每个数字的数量,然后递归并计算数十列中的数量等等。如果我们愿意,我们可以减去前导零。 / p>
如果您使用Excel写出数字但是为数字的每个数字使用单独的列,则更容易可视化
A B C
- - -
0 0 1 (assuming room numbers do not start at zero)
0 0 2
0 0 3
.
.
.
3 6 4
3 6 5
.
.
.
6 6 9
6 7 0
6 7 1
^
sum in columns not rows
因此,如果最高房间数为671,那么数百列将垂直有100个零,其次是100个,最多71个六,如果需要则忽略100个零,因为我们知道它们都是领先的。
然后递归到数十并执行相同的操作,我们知道将有10个零后跟10个等,重复6次,然后最后时间减少到2个七次。再次可以忽略前10个零,因为我们知道它们正在领先。当然最后是单位,根据需要忽略第一个零。
所以没有循环一切都是用除法计算的。我使用递归来“向上”移动列,直到达到最大值(在这种情况下为数百),然后在总计的情况下返回。
我在C#中写过这篇文章,并且如果有兴趣的话,可以发布代码,没有做任何基准测试时间,但对于最多10 ^ 18个房间的值,它基本上是即时的。
无法找到此处或其他地方提到的这种方法,所以认为它可能对某人有用。
答案 7 :(得分:1)
这不能回答您的确切问题,但有趣的是要注意根据Benford's Law的第一个数字的分布。例如,如果您随机选择一组数字,其中30%将以“1”开头,这有点违反直觉。
我不知道任何描述后续数字的分布,但您可以根据经验确定这一点,并提出一个简单的公式来计算任何范围所需的近似位数。号。
答案 8 :(得分:1)
如果“更好”意味着“更清楚”,那么我对此表示怀疑。如果它意味着“更快”,那么是的,但我不会使用更快的算法代替更清晰的算法而没有迫切需要。
#!/usr/bin/ruby1.8
def digits_for_range(min, max, leading_zeros)
bins = [0] * 10
format = [
'%',
('0' if leading_zeros),
max.to_s.size,
'd',
].compact.join
(min..max).each do |i|
s = format % i
for digit in s.scan(/./)
bins[digit.to_i] +=1 unless digit == ' '
end
end
bins
end
p digits_for_range(1, 49, false)
# => [4, 15, 15, 15, 15, 5, 5, 5, 5, 5]
p digits_for_range(1, 49, true)
# => [13, 15, 15, 15, 15, 5, 5, 5, 5, 5]
p digits_for_range(1, 10000, false)
# => [2893, 4001, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000]
Ruby 1.8,一种已知为“狗慢”的语言,在0.135秒内运行上述代码。这包括加载解释器。除非你需要更快的速度,否则不要放弃明显的算法。
答案 9 :(得分:1)
如果您需要多次迭代的原始速度,请尝试查找表:
int nDigits[10000][10] ; // Don't try this on the stack, kids!
n=0..9999:
if (n>0) nDigits[n] = nDigits[n-1]
d=0..9:
nDigits[n][d] += countOccurrencesOf(n,d) //
- Number of digits "between" two numbers becomes simple subtraction.
For range=51 to 300, take the counts for 300 and subtract the counts for 50.
0's = nDigits[300][0] - nDigits[50][0]
1's = nDigits[300][1] - nDigits[50][1]
2's = nDigits[300][2] - nDigits[50][2]
3's = nDigits[300][3] - nDigits[50][3]
etc.
答案 10 :(得分:0)
您可以分隔每个数字(look here for a example),创建一个直方图,其中包含0..9中的条目(将计算一个数字中出现的位数)并乘以要求的“数字”数。 / p>
但如果不是你想要的,你能举出更好的例子吗?
编辑:
现在我觉得我遇到了问题。我想你可以认为(伪C):
int histogram[10];
memset(histogram, 0, sizeof(histogram));
for(i = startNumber; i <= endNumber; ++i)
{
array = separateDigits(i);
for(j = 0; k < array.length; ++j)
{
histogram[k]++;
}
}
单独的数字实现链接中的功能。
直方图的每个位置都有每个数字的数量。例如
histogram[0] == total of zeros
histogram[1] == total of ones
...
此致