【问题标题】:Count the number of occurrences of 0's in integers from 1 to N计算从 1 到 N 的整数中 0 出现的次数
【发布时间】:2012-08-07 04:25:26
【问题描述】:

您将如何有效地计算从 1 到 N 整数的十进制表示中 0 的出现次数?

e.g. The number of 0's from 1 to 105 is 16. How?

10,20,30,40,50,60,70,80,90,100,101,102,103,104,105    

数一下 0 的个数,你会发现它是 16 个。

显然,蛮力方法不会受到赞赏。你必须想出一种方法,它不依赖于“有多少数字落在 1 到 N 之间”。 我们可以通过查看某种模式来完成吗?

我们不能扩展logic compiled here 来解决这个问题吗?

【问题讨论】:

  • 我以为你不应该在面试后泄露这些问题;)
  • 它显然与n^2 成正比,因此只需将其暴力破解到 10k 或 100k 即可导出一个常数,然后就完成了。
  • @DeadMG,我想说的是N log N的顺序。
  • 我喜欢这类问题。该算法在课堂外实际应用的机会 0.00000000000000000000000000001%。
  • 要在一个范围内分配数字,对它们进行计数会有所帮助。 en.wikipedia.org/wiki/Benford's_law

标签: c algorithm math


【解决方案1】:

更新答案

我最初的答案很容易理解,但很难编写代码。这是更容易编码的东西。这是一个直接的非递归解决方案,它通过计算每个位置出现零的次数来工作。

例如:

x

x = ??0?

“数百或更多”有 12 种可能性(1,2, ..., 12)。那么必须有一个零。那么最后一位数字有 10 种可能性。这给出了12 * 10 = 120 在第三位数字处包含 0 的数字。

因此,范围(1 到 1234)的解是:

  • ?0??: 1 * 100 = 100
  • ??0?: 12 * 10 = 120
  • ???0:123
  • 总计 = 343

但一个例外是n 包含一个零数字。考虑以下情况:

x

x = ??0??

我们有 12 种方法来挑选“数千个或更多”。对于 1, 2, ... 11,我们可以选择任意最后两位数字(给出 11 * 100 种可能性)。但是如果我们从 12 开始,我们只能在最后两位数字中选择一个介于 0034 之间的数字。所以我们得到了11 * 100 + 35 的全部可能性。


这是该算法的一个实现(用 Python 编写,但应该很容易移植到 C):

def countZeros(n):
    result = 0
    i = 1

    while True:
        b, c = divmod(n, i)
        a, b = divmod(b, 10)

        if a == 0:
            return result

        if b == 0:
            result += (a - 1) * i + c + 1
        else:
            result += a * i

        i *= 10

【讨论】:

  • 对于 1 到 999,您的“结束条件的特殊处理”涵盖了 90% 的范围(100 到 999)。这不是一个很好的答案...
  • @Nemo,我认为你误解了那部分。规则是将数字分成具有相等位数的范围,因此 [1,999] 变为 ([1,9],[10,99],[100,999]),然后对于您执行的每个段 (high-low) *(length-1)/10,并将它们加在一起。 “结束条件的特殊处理”将是如果不是 [1,999] 我们有类似 [1,1234] 的东西,其中 [1000,1234] 不适合模式。只有最后一部分需要特殊处理。
  • @Kevin:是的,我在想 (1,666) 什么的,但我决定破产和过度扩张。
  • @Mark:好的,所以把它编码,以便我们可以将它与其他解决方案进行比较:-)
  • 现在您已经提供了代码并对其进行了调试,我将删除我的反对票。我仍然认为递归公式更简单,但我同意这行得通。附言@drewk 的“期望”或许更适合称为“给出正确答案”
【解决方案2】:

我建议将此算法从以 2 为基数调整为以 10 为基数:

Number of 1s in the two's complement binary representations of integers in a range

得到的算法是 O(log N)。

方法是编写一个简单的递归函数count(n),从1 到n 计数零。

关键的观察是,如果 N 以 9 结尾,例如:

123456789

您可以将 0 到 N 的数字分成 10 个大小相等的组。第 0 组是以 0 结尾的数字。第 1 组是以 1 结尾的数字。第 2 组是以 2 结尾的数字。以此类推,一直到第 9 组,即所有以 9 结尾的数字。

除了第 0 组之外的每个组都将count(N/10) 的数字贡献到总数中,因为它们都没有以零结尾。第 0 组贡献了count(N/10)(计算除最后一位以外的所有数字)加上N/10(计算最后一位数字中的零)。

由于我们是从 1 到 N,而不是从 0 到 N,因此对于一位数 N,这个逻辑会失效,所以我们只是将其作为一种特殊情况来处理。

[更新]

什么鬼,让我们将count(n, d) 概括并定义为数字d 在从1 到n 的数字中出现的次数。

/* Count how many d's occur in a single n */
unsigned
popcount(unsigned n, unsigned d) {
  int result = 0;
  while (n != 0) {
    result += ((n%10) == d);
    n /= 10;
  }
  return result;
}

/* Compute how many d's occur all numbers from 1 to n */
unsigned
count(unsigned n, unsigned d) {
  /* Special case single-digit n */
  if (n < 10) return (d > 0 && n >= d);

  /* If n does not end in 9, recurse until it does */
  if ((n % 10) != 9) return popcount(n, d) + count(n-1, d);

  return 10*count(n/10, d) + (n/10) + (d > 0);
}

n &lt; 10 的丑陋再次来自 1 到 n 的范围,而不是 0 到 n... count 为 1,除非 d 为零。

将此解决方案转换为非递归循环是 (a) 微不足道的,(b) 不必要的,并且 (c) 留给读者作为练习。

[更新 2]

最后的 (d &gt; 0) 术语也来自 1 到 n 而不是 0 到 n 的范围。当n以9结尾时,1到n之间有多少个数字有最后一位d?好吧,当d 为零时,答案是n/10;当d 不为零时,它会比它大一,因为它本身包含值d

例如,如果n 是 19,d 是 0,则只有一个以 0 结尾的较小数字(即 10)。但是如果n 是 19 而d 是 2,则有两个以 2 结尾的较小数字(即 2 和 12)。

感谢@Chan 指出 cmets 中的这个错误;我已经在代码中修复了它。

【讨论】:

  • @Nemo,我发布了一个解决方案,我认为它与您的建议类似。我正在努力推广我的解决方案。你能帮我吗?谢谢
  • @Nemo:你测试过你的解决方案了吗?对于 n = 20d = 2,有 3 个 2(2、12、20),但您的解决方案只产生了 2 个。
  • @Chan:确实,只要 d 非零,我就有一个错误。固定的;谢谢。
【解决方案3】:

Z(n) = #zero digits in numbers 0 &lt;= k &lt; n。显然,Z(0) = 0

如果n = 10*k + r, 0 &lt;= r &lt;= 9,则所有10*k 数字10*j + s, 0 &lt;= j &lt; k, 0 &lt;= s &lt;= 9 都在该范围内,每十位最后一位为0,因此k 为零,并且每个前缀j(除最后一位之外的所有数字)出现十次,但我们不能数0,所以前缀中的零个数是10*(Z(k)-1)

r 数字10*k, ..., 10*k + (r-1) 中零的个数是r*number of zeros in k + (r &gt; 0 ? 1 : 0)

所以我们有一个O(log n) 算法来计算Z(n)

unsigned long long Z(unsigned long long n)
{
    if (n == 0) {
        return 0;
    }
    if (n <= 10) {
        return 1;
    }
    unsigned long long k = n/10, r = n%10;
    unsigned long long zeros = k + 10*(Z(k)-1);
    if (r > 0) {
        zeros += r*zeroCount(k) + 1;
    }
    return zeros;
}

unsigned zeroCount(unsigned long long k)
{
    unsigned zeros = 0;
    while(k) {
        zeros += (k % 10) == 0;
        k /= 10;
    }
    return zeros;
}

要计算任意范围的数字,

unsigned long long zeros_in_range(unsigned long long low, unsigned long long high)
{
    return Z(high+1) - Z(low); // beware of overflow if high is ULLONG_MAX
}

【讨论】:

  • @BLUEPIXY 忘了减去 0 前缀,谢谢提醒。
  • @BLUEPIXY 不,344 是正确的,Prelude&gt; length . filter (== '0') $ [0 .. 1233] &gt;&gt;= show 给出 344。Z(n) 计算 0, 1, ..., n-1 中的零,因此对于所有 n &gt; 0,它必须至少为 1。
【解决方案4】:

我有一个非常简单的方法来计算 1 到 n 之间的 0。 我希望这能解决您的问题并消除复杂性

function countZero(n) {
  var count = 0;
  while (n > 0) {
    count += Math.floor(n / 10);
    n = n / 10;
  }
  console.log(count);
}

countZero(99);

【讨论】:

    【解决方案5】:
    class FindZero{
    
        public int findZero(int lastNumber){
    
            int count=1,k;
            if(lastNumber<10)
                return 0;
            else if(lastNumber==10)
                return 1;
            else{
    
                for(int i=11;i<=lastNumber;i++){
                    k=i;
                    while(k>0){
    
                        if(k%10==0)
                            count++;
                            k=k/10;
                    }
                }
                return count;
            }
        }
        public static void main(String args[]){
            FindZero obj = new FindZero();
            System.out.println(obj.findZero(1234));
        }
    }
    

    【讨论】:

      【解决方案6】:

      我解决这个问题的方式:

      数字可以在 1 到 N 的范围内:

      所以,我把它分成这样的范围:

      Rangle      : #Digits   :   #Zeros
      1   -   9   :   1       :   0
      10  -   99  :   2       :   9 (number of all the possible digits when zero is at units place=> _0 ie, 1,2,3,4,5,6,7,8,9
      100 -   199 :   3       :   20 => 10 (#digits when zero is at units place) + 10 (#digits when zero is at tens place)
      200 -   276 :   3       :   18 => 8 (#digits when zero is at units place) + 10 (#digits when zero is at tens place)
      300 -   308 :   3       :   10 => 1 (#digits when zero is at units place) + 9 (#digits when zero is at tens place)
      1000-   1008:   4       :   19 => 1 + 9 + 9
      

      现在对于任何给定的范围 1 - N,我希望能够将数字分解为这些范围并使用上述逻辑来计算零的数量。

      试运行:

      对于给定的数字 N:

      - compute number of digits: len
      - if len = 1 : d1: return 0
      - len = 2: d2_temp: count # of digits that can possibly occur when 0 is at unit's place 
                  : for e.g. 76: so numbers can be between 10 - 76: (7 - 1) + 1 = 7
               : d2: sum(d2_temp, d1)
      - len = 3: return d3 : sum(d3_temp, d2)
               : compute d3_temp: 
               : for e.g. n = 308 : get digit at 10^(len-1) : loopMax 3
               : d3_temp1: count number of zeros for this loop: 1 * 100 to (loopMax -1) * 100 : (loopMax-1) * 20
               : d3_temp2: for n count (#digits when zero is at units place) + (#digits when zero is at tens place)
               : d3_temp = d3_temp1 + d3_temp2
      

      让我们试着概括一下:

      99 : sum( , )
          : d3_temp: 
          : loopMax: n = 99 : n/(10^1) : 9
          : d3_temp1: 8 : (9-1) * (10*(len-1)) : (loopMax - 1) * 10 * (len-1)
          : d3_temp2: 1 : for len, count #0s in range (loopMax * 10 * (len-1)) to n : count(90, 99)
          : d3_temp = 8 + 1
          : sum(9, 0)
          : 9
      

      我在从这里开始时遇到了一些问题,但这会起作用。

      【讨论】:

      • @Nemo:我认为这与您的建议相似。你能帮我想出一个概括这个来实现它的功能吗?
      猜你喜欢
      • 2015-09-16
      • 2022-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多