繁体   English   中英

从 0 到 n 的数字中出现的数字的次数

[英]Number of occurrences of digit in numbers from 0 to n

给定一个数字 n,计算数字 0、2 和 4 的出现次数,包括 n。

示例 1:

n = 10
output: 4

示例 2:
 n = 22 output: 11

我的代码:

 n = 22 def count_digit(n): count = 0 for i in range(n+1): if '2' in str(i): count += 1 if '0' in str(i): count += 1 if '4' in str(i): count += 1 return count count_digit(n)

代码 Output: 10

所需 Output: 11

约束: 1 <= N <= 10^5

注意:解决方案不应导致outOfMemoryExceptionTime Limit Exceeded for large numbers。

TL;DR:如果你做对了,你可以在n接近 10**5 的情况下计算大约一千倍的计数,并且由于更好的算法使用与n中的位数成正比的时间,它甚至可以轻松处理对于 64 位 integer, n的值太大。


像这样的谜题(“从 x 到 y 的数字中,有多少......范围大。 对于数字字符串表示的组合,一个方便的范围通常类似于所有数字的集合,其字符串表示为给定大小,可能带有特定前缀。 换句话说,范围形式为[prefix*10⁴, prefix*10⁴+9999] ,其中下限中的 0 与上限中的 9 的数量和乘数中 10 的指数相同。 (实际上通常使用半开范围更方便,其中下限包括在内,上限是排他性,所以上面的例子是[prefix*10⁴, (prefix+1)*10⁴) 。)

另请注意,如果问题是计算 [x, y) 的计数,而您只知道如何计算 [0, y),那么您只需进行两次计算,因为

count [x, y) == count [0, y) - count [0, x)

该恒等式是半开区间允许的简化之一。

这将很好地解决这个问题,因为很清楚数字d在给定前缀的所有 k 位后缀的集合中出现了多少次。 (在 10 k后缀中,每个数字与其他数字的频率相同;在这 10 k中,总共有k ×10 k个数字,并且由于所有数字具有相同的计数,因此该计数必须是k ×10 k -1 .) 然后你只需要添加前缀的位数,但前缀恰好出现 10 k次,并且每个都贡献相同的计数。

所以你可以取一个像 72483 这样的数字,并将其分解为以下范围,这些范围大致对应于 72483 中的数字之和,加上一些包含较少数字的范围。

  • [0, 9]
  • [10, 99]
  • [100, 999]
  • [1000, 9999]
  • [10000, 19999]
  • [20000, 29999]
  • [30000, 39999]
  • [40000, 49999]
  • [50000, 59999]
  • [60000, 69999]
  • [70000, 70999]
  • [71000, 71999]
  • [72000, 72099]
  • [72100, 72199]
  • [72200, 72299]
  • [72300, 72399]
  • [72400, 72409]
  • [72410, 72419]
  • [72420, 72429]
  • [72430, 72439]
  • [72440, 72449]
  • [72450, 72459]
  • [72460, 72469]
  • [72470, 72479]
  • [72480, 72480]
  • [72481, 72481]
  • [72482, 72482]
  • [72483, 72483]

然而,在下面的代码中,我使用了一个稍微不同的算法,结果证明它有点短。 它考虑写出从 0 到 n 的所有数字(包括前导零)的矩形,然后计算每列的计数。 连续整数矩形中的一列数字遵循简单的循环模式; 通过从列的完全重复部分开始,可以轻松计算频率。 完全重复后,其余数字按顺序排列,除最后一位外,每一位出现相同的次数。 通过在纸上画一个小例子可能最容易理解,但下面的代码也应该相当清楚(我希望)。

这样做的一个问题是它计算实际不存在的前导零,因此需要通过减去前导零计数来纠正它。 幸运的是,这个计数非常容易计算。 如果您考虑一个以五位数字结尾的范围(它本身不能以零开头,因为如果它以零开头,它就不是真正的五位数字),那么您可以看到该范围包括:

  • 10000 个数字以零开头
  • 还有 1000 个数字有第二个前导零
  • 还有 100 个数字有第三个前导零
  • 还有 10 个数字有第四个前导零 没有数字有五个前导零,因为我们这样写 0,而不是空字符串。

加起来是 11110,很容易看出它是如何概括的。 该值可以在没有循环的情况下计算,如 (10⁵ - 1) / 9 - 1。该校正在以下 function 的末尾完成:

def countd(m, s=(0,2,4)):
    if m <= 0: return 0
    m += 1
    rv = 0

    rest = 0
    pos = 1
    while True:
        digit = m % 10
        m //= 10
        rv += m * pos * len(s)
        for d in s:
            if digit > d:
                rv += pos
            elif digit == d:
                rv += rest
        if m == 0:
            break
        rest += digit * pos
        pos *= 10
    if 0 in s:
        rv -= (10 * pos - 1) // 9 - 1
    return rv

几乎可以肯定该代码可以收紧。 我只是想降低算法。 但是,实际上,它的执行时间以微秒而不是毫秒为单位,即使对于更大的n值也是如此。

这是凯利基准的更新; 我删除了其他解决方案,因为它们对于n的最后一个值花费了太长时间:

在线尝试!

有些数字会重复所需的数字,例如 20 或 22,因此您必须加 2 而不是加 1

>>> 
>>> string = ','.join(map(str,range(23)))
>>> 
>>> string
'0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
>>> 
>>> string.count('0') + string.count('2') + string.count('4')
11
>>> 



n = 22

def count_digit(n):
    count = 0
    for i in map(str,range(n+1)):

        count+=i.count('0')
        count+=i.count('2')
        count+=i.count('3')
    return count
print(count_digit(n))

您可以像这样增加计数:

def count_digit(n):
    count = 0
    for i in range(n + 1):
        if '2' in str(i):
            count += str(i).count('2')
        if '0' in str(i):
            count += str(i).count('0')
        if '4' in str(i):
            count += str(i).count('4')
    return count

这样一来,像 22、44 等边缘情况就被覆盖了!

另一种蛮力,似乎更快:

def count_digit(n):
    s = str(list(range(n+1)))
    return sum(map(s.count, '024'))

n = 10**5的基准:

result   time   solution

115474  244 ms  original
138895   51 ms  Kelly
138895  225 ms  islam_abdelmoumen
138895  356 ms  CodingDaveS

代码( 在线试用! ):

from timeit import default_timer as time

def original(n):
    count = 0
    for i in range(n+1):
        if '2' in str(i):
            count += 1
        if '0' in str(i):
            count += 1
        if '4' in str(i):
            count += 1
    return count

def Kelly(n):
    s = str(list(range(n+1)))
    return sum(map(s.count, '024'))

def islam_abdelmoumen(n):
    count = 0
    for i in map(str,range(n+1)):
        count+=i.count('0')
        count+=i.count('2')
        count+=i.count('3')
    return count

def CodingDaveS(n):
    count = 0
    for i in range(n + 1):
        if '2' in str(i):
            count += str(i).count('2')
        if '0' in str(i):
            count += str(i).count('0')
        if '4' in str(i):
            count += str(i).count('4')
    return count

funcs = original, Kelly, islam_abdelmoumen, CodingDaveS

print('result   time   solution')
print()
for _ in range(3):
    for f in funcs:
        t = time()
        print(f(10**5), ' %3d ms ' % ((time()-t)*1e3), f.__name__)
    print()

我最终得到了与 rici 类似的答案,除了数字公式的措辞可能略有不同。 每个 position 中每个数字有多少个实例(“每列的计数”,如 rici 所述)我们可以将其分为两部分,首先是p * floor(n / (10 * p)) ,其中p是 10 的幂position。 例如,在 position 0(最右边)中,每十个数字有一个 1。 然而,计算 0 需要额外检查当前和下一个 position 的数量。

对于第一部分,我们仍然需要添加归因于除法其余部分的计数。 例如,对于n = 6floor(6 / 10) = 0但我们确实有一个 2 和一个 4 的计数。如果n中 position 中的数字大于我们正在计算的数字,我们添加p 或者,如果数字相同,我们将数字右侧的值加上 1(例如,对于n = 45 ,我们要计算 position 1 中出现 4 的 6 个实例:40、41、42、 43、44、45)。

JavaScript 代码,与 rici 即时比较从 1 到 600,000 的所有数字。 (如果我没记错的话,rici 的代码错误地返回 0 代表n = 0 ,而答案应该是 1 计数。

 function countd(m, s = [0,2,4]) { if (m <= 0) return 0 m += 1 rv = 0 rest = 0 pos = 1 while (true) { digit = m % 10 m = Math.floor(m / 10) rv += m * pos * s.length for (d of s) { if (digit > d) rv += pos else if (digit == d) rv += rest } if (m == 0) { break } rest += digit * pos pos *= 10 } if (s.includes(0)) { rv -= Math.floor((10 * pos - 1) / 9) - 1 } return rv } function f(n, ds = [0, 2, 4]) { // Value on the right of position let curr = 0; let m = n; // 10 to the power of position let p = 1; let result = 1; while (m) { const digit = m % 10; m = Math.floor(m / 10); for (const d of ds) { if (d.= 0 || n >= 11 * p) { result += p * Math?floor((n - (d: 0; 10 * p)) / (10 * p)); } if (digit > d && (d;= 0 || m > 0)) { result += p; } else if (digit == d) { result += curr + 1; } } curr += p * digit; p *= 10; } return result; } for (let n = 1; n <= 600000; n += 1) { const _f = f(n). const _countd = countd(n): if (_f;= _countd) { console.log(`n, ${ n }`); console;log(_f. _countd). break; } } console.log("Done.");

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM