【问题标题】:How to determine how many times '0' to '9' have been used in a specific range instantly如何立即确定在特定范围内使用了多少次“0”到“9”
【发布时间】:2020-06-01 02:34:55
【问题描述】:

我想计算在特定范围内使用了多少次“0”到“9”

例如112有两个'1'和一个'2'

感谢您的建议,我现在更改了代码。

但它没有我想要的效率。

num = int(input('n : '))
start_time = time.time()
num_arr = [0] * 10
progress_arr = ['%d' % (num * (i / 100)) for i in range(1, 101)]

for i in range(1, num + 1):
    if str(i) in progress_arr:
        print(str(progress_arr.index(str(i)) + 1) + '%')
    for digit in str(i):
        num_arr[int(digit)] += 1

print('Time : %.2f' % (time.time() - start_time))
print(num_arr)

当我输入 1000 作为 n 时,我的程序会立即打印这个 arr

[192, 301, 300, 300, 300, 300, 300, 300, 300, 300]

表示从 1 到 1000, '0' 被使用了 192 次,'1' 被使用了 301 次和···

但是当我输入 2,000,000,000 作为 n 时,我的程序将在之后打印结果 11 分钟(使用 pypy3,Ryzen 2600)。

所以我想更改我的代码,以尽可能立即获得非常大的数字的结果。

在我之前的问题中,我提到了 O(N) 的时间复杂度,因为我对算法和时间复杂度的理解很差,这让你感到困惑。所以我删了。

我想知道的只是高效算法

我正在尝试通过分析增量结果的差异来找到一个特定的规则,但这并不容易。

【问题讨论】:

  • 你需要像数字动态规划这样的东西。看看这个教程:geeksforgeeks.org/digit-dp-introduction
  • 你的程序远比 O(n^2) 差。考虑一下您 a) 构建每个不同数字的流表示,然后 b) 迭代整个字符串的次数。
  • 你的代码已经是O(N)了,问题是N很大
  • 我现在更改了我的代码。
  • get the result of 'n : 2 billion' instantly. N : the length of the input number (number of digits) -- 你真的想立即获得 20 亿位数的结果吗?

标签: python python-3.x algorithm numbers


【解决方案1】:

为了有效地解决这个问题,你不能一个一个地遍历数字并计算它们的位数。你需要应用数学。


假设范围的上限是 123456。让我们试着算出每个可能的数字在这个范围内出现的次数,在数百个位置。

对于每 1000 个连续整数,每个可能的数字在百位中出现 100 次,除非该数字是前导零。有 123456//1000=123 个完整的 1000 整数块,剩下从 123001 到 123456 的 123456%1000=456 个整数。完整的块为我们提供了 123*100=12300 次出现在数百位中的每个数字,由于前导零,数字 0 为负 99。

在123001到123456的范围内,0在百位中出现99次,1到3每个数字出现100次,4出现57次。


弄清楚如何将该逻辑形式化到足以在程序中实现它,并将其应用于每个数字位置,您将能够轻松处理远大于 20 亿的输入。

【讨论】:

  • 感谢您的建议。我会试试的!
【解决方案2】:

嗯,我有一种方法,输入2,000,000,000 需要大约 30-40 分钟,这样够快吗?希望那是在你死之前很久!它在我的机器上每秒处理大约 1,000,000 个数字。关键是您在每个号码上呼叫count() 10 次,每个数字一次。但是,如果您只遍历一次数字并根据您在每个位置看到的数字递增,那么您只需遍历每个数字一次。我的方法也不会在内存中保留太多,所以它不会用完空间(我选择使用 dict 而不是数字列表,但应该差不多)。我的代码:

num = 2 * (10**9) # 2 billion
num_dict = {}
for i in range(10):       # Dict to keep track of counts
    num_dict[str(i)] = 0

for i in range(1, num + 1):
    if i%1000000 == 0:           # print progress every Million
        print(f'Processed {i/1000000}M numbers')
    for digit in str(i):
        num_dict[digit] += 1

>>> print(num_dict)
{
    '0': 1688888898,
    '1': 2800000000,
    '2': 1800000001,
    '3': 1800000000,
    '4': 1800000000,
    '5': 1800000000,
    '6': 1800000000,
    '7': 1800000000,
    '8': 1800000000,
    '9': 1800000000
}

如果你愿意,我会在 40 分钟内更新答案
n : 2billion的答案在上面! ;D

编码愉快!

【讨论】:

  • 现在我可以得到 n : 20 亿的结果,但是,我想知道的是一种足够高效的算法,可以立即得到 20 亿的结果。但是感谢您的解决方案,我的代码效率提高了很多。感谢您的帮助!
  • 哦,你的问题是你想要一个 O(N) 而不是你当前的 O(N^2),所以这就是我解决的问题。啊,你想要一种巧妙的捷径并立即得到答案!这将对计数如何增加进行一些分析,如果我有时间我会再次回答,但现在必须这样做。 n 的答案:超过 20 亿!
  • 如果 n 是位数而不是输入的数值,则此算法也是 O(n^2):In [54]: %timeit sam(10) - 8.14 µs ; In [55]: %timeit sam(100) - 57 µs ; In [56]: %timeit sam(1000) - 689 µs。它与我上面算法的更快版本 (2) 的速度大致相同。
  • 我想出了一种立即获得准确计数的方法,如果您通过 10 的幂求和,请查看我的其他答案!
【解决方案3】:

好的,我试了一下,甚至是 s-m-r-t'er。我看了一堆 10 次方的计数:

At 10 totaldigits=11, counts are: [1, 2, 1, 1, 1, 1, 1, 1, 1, 1]
At 100 totaldigits=192, counts are: [11, 21, 20, 20, 20, 20, 20, 20, 20, 20]
At 1000 totaldigits=2893, counts are: [192, 301, 300, 300, 300, 300, 300, 300, 300, 300]
At 10000 totaldigits=38894, counts are: [2893, 4001, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000]
At 100000 totaldigits=488895, counts are: [38894, 50001, 50000, 50000, 50000, 50000, 50000, 50000, 50000, 50000]
At 1000000 totaldigits=5888896, counts are: [488895, 600001, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000]
At 10000000 totaldigits=68888897, counts are: [5888896, 7000001, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000]

一种模式已经开始出现!在10^n 数字之后,2,3,...,9 的计数等于n * 10^(n-1)1 的计数比 n * 10^(n-1) + 1 多 1。 0 的计数小于其他计数,并且似乎等于前十次方的总位数!因此,我们可以将数字中的位数数到 10,然后对于之后的每个幂,可以直接计算 1 到 9 的计数,并且可以从之前的总计数中获得 0 的计数。即:

total_digits_in = {}
total_digits_in[10] = 11
highest_power = 9
for cur_power in range(2, highest_power+1):
    counts = [
        total_digits_in[10**(cur_power-1)],     # zero = total counts at previous power of 10
        cur_power * (10**(cur_power - 1)) + 1,  # one = n * 10^(n-1) + 1
        cur_power * (10 ** (cur_power - 1)) ,   # 2 thru 9 = n * 10^(n-1)
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
        cur_power * (10 ** (cur_power - 1)),
    ]
    total_digits_in[10**cur_power] = sum(counts)
    print(f'Counts for 10^{cur_power}: {counts}')

Counts for 10^2: [11, 21, 20, 20, 20, 20, 20, 20, 20, 20]
Counts for 10^3: [192, 301, 300, 300, 300, 300, 300, 300, 300, 300]
Counts for 10^4: [2893, 4001, 4000, 4000, 4000, 4000, 4000, 4000, 4000, 4000]
Counts for 10^5: [38894, 50001, 50000, 50000, 50000, 50000, 50000, 50000, 50000, 50000]
Counts for 10^6: [488895, 600001, 600000, 600000, 600000, 600000, 600000, 600000, 600000, 600000]
Counts for 10^7: [5888896, 7000001, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000, 7000000]
Counts for 10^8: [68888897, 80000001, 80000000, 80000000, 80000000, 80000000, 80000000, 80000000, 80000000, 80000000]
Counts for 10^9: [788888898, 900000001, 900000000, 900000000, 900000000, 900000000, 900000000, 900000000, 900000000, 900000000]

如果遍历从 1 到 10 的幂的整数,这基本上可以立即为您提供每个数字的确切计数。我认为要获得 10 次幂之间的任意数字的计数将变得更加困难。我认为这不是不可能的,只是......很难。总之,希望你喜欢这个新的解决方案!

【讨论】:

    【解决方案4】:

    使用collections.Counter 计算范围内每个数字中数字的出现次数。通过在循环遍历范围时保持计数,您可以一次性完成。

    def digits(number):
        c = Counter()
        for n in range(1, number+1):
            c.update(str(n))
    
        # return [c[str(k)] for k in range(10)]
        return c
    

    输出:

    >>> digits(1000)
    Counter({'1': 301, '2': 300, '3': 300, '4': 300, '5': 300, '6': 300, '7': 300, '8': 300, '9': 300, '0': 192})
    

    如果您希望它作为从 0 到 9 的数字列表,则将返回行更改为:

    return [c[str(k)] for k in range(10)]
    
    >>> digits(1000)
    [192, 301, 300, 300, 300, 300, 300, 300, 300, 300]
    

    至于复杂性:

    In [8]: %timeit digits(10)
    16.8 µs ± 151 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [9]: %timeit digits(100)
    113 µs ± 1.22 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    In [10]: %timeit digits(1000)
    1.14 ms ± 8.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    输入值增加 10 倍,所用时间增加 10 倍。这是 O(n),而不是 O(n^2)。

    如果您定义“n”不是作为输入数字,而是作为输入数字的长度(位数),那么是的,它是 O( n^2)。但这是关于“n”是什么的任意决定。按照这个标准,所有当前为 O(n) 的数值算法将立即变为 O(n^2)。 (我想定义'n'的基础是灵活的。)


    这是相同函数的一个稍微快一点的版本,但具有相同的时间复杂度:

    def digits(number):
        c = Counter(char for n in range(1, number+1) for char in str(n))
        return c  # or as one line
    
    In [19]: %timeit digits(10)
    7.15 µs ± 231 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    In [20]: %timeit digits(100)
    47.5 µs ± 1.81 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    In [21]: %timeit digits(1000)
    606 µs ± 48.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    【讨论】:

    • collections.Counter() 因此在更新中使用循环。不能说它是 O(n)
    • 存在相关性,但到目前为止我找不到。此代码也具有 O(n^2) 时间复杂度。所以我无法得到'n:20亿'的结果
    • 是的,“N”是输入数字的长度(位数)。我为我含糊不清的解释感到抱歉。而我想知道的是一种算法,它的效率足以得到 20 亿的结果。但感谢您的考虑。
    • 根据我对 Sam 的回答的评论,我们的两个解决方案都是 O(n^2)。我已经记录了 10、100 和 1000 的执行时间。使用 number=1000 (606 µs) 的时间。 2,000,000,000 大约需要 606 µs * 1,000,000 * 2 = 20.2 minutes
    • 感谢您的考虑。我认为我对算法和时间复杂度的理解很差,让你感到困惑。所以我删除了它。而现在我正试图找到并形式化可以得到大量结果的特定规则。谢谢!
    【解决方案5】:

    我终于做到了!

    def main():
        n = int(input('n : '))
        num_arr = [0] * 10
        w = 1
    
        for step in range(len(str(n))):
            remaining = 9 - int(str(n)[-1:])
    
            for i in range(len(num_arr)):
                num_arr[i] += (n // 10 + 1) * w
    
            for i in range(10 - remaining, 10):
                num_arr[i] -= w
            num_arr[0] -= w
    
            for number in str(n)[:-1]:
                num_arr[int(number)] -= remaining * w
    
            n //= 10
            w *= 10
    
        print(num_arr)
    
    
    if __name__ == '__main__':
        main()
    

    现在我无法得到比 1 秒快 20 亿的结果!哈哈

    【讨论】:

      猜你喜欢
      • 2013-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-15
      • 1970-01-01
      • 2017-10-16
      相关资源
      最近更新 更多