【问题标题】:Count number of 1's in binary representation以二进制表示计数 1 的个数
【发布时间】:2012-02-10 20:59:08
【问题描述】:

如果您有足够的内存可以使用,那么在 O(1) 中计算数字的二进制表示中 1 的数量的有效方法。这是我在一个在线论坛上找到的一个面试问题,但没有答案。有人可以提出一些建议吗,我想不出在 O(1) 时间内完成的方法?

【问题讨论】:

  • 所以这个问题要你作弊——“足够”的内存很容易比可观察宇宙中的原子多。
  • 不就是一个MAX_INT长度的数组吗?
  • 请纠正我 - 如果我们有一个数组 [0..MAX_INT-1] ,其中索引是输入的实际数字,数据是该数字的 1 的数量(假设它是通过内容寻址内存实现的 en.wikipedia.org/wiki/Content-addressable_memory) 不是 O(1) 吗?
  • 这可能是模型面试解决方案,虽然我不认为它会满足 purist 因为它受到机器上可寻址内存的数据宽度的限制(比如 64 位)。它不适用于> 2^64 的数字,并且如前所述,该问题并未施加此限制。如果问题被修改为“64 位数字”,那么是的,这是一个很好的解决方案。
  • 另一方面,对于 64 位数字,旧的位计数方法也是 O(1) 并且几乎不使用内存。

标签: algorithm binary


【解决方案1】:

这就是Hamming weight 问题,也就是人口数量。该链接提到了有效的实现。引用:

有了无限的内存,我们可以简单地创建一个每个 64 位整数的汉明权重的大型查找表

【讨论】:

  • +1 用于正确命名此问题。虽然我认为一个完整的答案会说明即使使用查找表也不会是 O(1) 时间,因为查找任何条目的时间取决于条目的大小。
  • @TimGee LUT 访问始终被视为 O(1)。当您拥有无限的内存时,您也拥有无限的地址总线。因此,无论您访问哪个地址(= 输入),它始终只是一次内存访问;-)
  • @Scolytus,任何关于“当你有无限的内存,你也有一个无限的地址总线”的引用?
  • @sasha.sochka 无限内存是一种理论结构。它根本没有地址总线,只是假设您总是有足够的内存并且总是可以以相同的方式访问它,而与它的大小无关。因此,在无限内存的实际实现中,您的地址总线将始终为 > log2(count(addressable_memory_units))。
  • @JackAidley 链接在哪里?
【解决方案2】:

我有一个解决方案可以计算O(Number of 1's) 时间的位数:

bitcount(n):
    count = 0
    while n > 0:
        count = count + 1
        n = n & (n-1)
    return count

在最坏的情况下(当数字为 2^n - 1 时,二进制全为 1)它会检查每一位。

编辑: 刚刚找到了一个非常好的用于位计数的恒定时间、恒定内存算法。在这里,用 C 语言编写:

int BitCount(unsigned int u)
{
     unsigned int uCount;

     uCount = u - ((u >> 1) & 033333333333) - ((u >> 2) & 011111111111);
     return ((uCount + (uCount >> 3)) & 030707070707) % 63;
}

你可以找到它的正确性证明here

【讨论】:

  • 本题的思路是使用平坦时间,即无论是零个、一个还是多个1,运行时间都没有差异。
  • 第二个只是常数时间,因为输入的大小是常数,在这种情况下,第一个也是常数。第一个算法在一般情况下实际上是 O(log n),因为按位与和减法需要很长时间。
  • 您的第一个答案是 O(log n) 而不是 O(1)。您的第二个答案是 O(1),但假定域为 32 位(输入参数为 unsigned int)。
  • 或者有没有人知道另一个有证明的页面?
  • 如果有人想知道 n = n & (n-1) 是做什么的,它会清除 n 的最低有效位 (1)。当 n=0 时非常有效,我们在 while 内循环了“回答”时间。
【解决方案3】:

请注意以下事实:n&(n-1) 总是消除最不重要的 1。

因此我们可以编写如下代码来计算 1 的个数:

count=0;
while(n!=0){
  n = n&(n-1);
  count++;
}
cout<<"Number of 1's in n is: "<<count;

程序的复杂性是:n 中 1 的数量(始终小于 32)。

【讨论】:

    【解决方案4】:

    我从另一个网站看到了以下解决方案:

    int count_one(int x){
        x = (x & (0x55555555)) + ((x >> 1) & (0x55555555));
        x = (x & (0x33333333)) + ((x >> 2) & (0x33333333));
        x = (x & (0x0f0f0f0f)) + ((x >> 4) & (0x0f0f0f0f));
        x = (x & (0x00ff00ff)) + ((x >> 8) & (0x00ff00ff));
        x = (x & (0x0000ffff)) + ((x >> 16) & (0x0000ffff));
        return x;
    }
    

    【讨论】:

      【解决方案5】:
      public static void main(String[] args) {
      
          int a = 3;
          int orig = a;
          int count = 0;
          while(a>0)
          {
              a = a >> 1 << 1;
              if(orig-a==1)
                  count++;
              orig = a >> 1;
              a = orig;
          }
      
          System.out.println("Number of 1s are: "+count);
      }
      

      【讨论】:

      • +1 好答案。尝试添加一些解释。尤其是双向的位移部分,这可能会让一些人感到困惑。
      • 解释:A) 如果最低有效位为 1,则将计数器加 1(这部分的工作原理如下:右移,然后撤消移位将最低有效位设置为 0...如果旧数字之间的差异并且新数字是 1,然后删除 1)B)将数字除以 2 并通过右移 1 C)重复直到数字为 0。将数字与 1 相加来确定是否最不重要会更容易数字是 1。
      • 嗨,我只是好奇,if (orig &amp; 1)count++;orig &gt;&gt;= 1; 效率会不会高一点?
      • 或者更好:count += orig &amp; 1;orig &gt;&gt;= 1;
      【解决方案6】:
         countBits(x){
           y=0;
           while(x){   
             y += x &  1 ;
             x  = x >> 1 ;
           }
         }
      

      就这样?

      【讨论】:

        【解决方案7】:

        这将是我生命中最短的答案:查找表。

        显然,我需要解释一下:“如果你有足够的内存可以玩”意味着我们已经拥有了我们需要的所有内存(没关系技术可能性)。现在,您不需要存储超过一两个字节的查找表。虽然从技术上讲它是 Ω(log(n)) 而不是 O(1),但只需读取您需要的数字就是 Ω(log(n)),所以如果这是一个问题,那么答案是,不可能——甚至更短。

        他们希望在面试中从你那里得到两个答案中的哪一个,没人知道。

        还有另一个技巧:虽然工程师可以取一个数字并讨论 Ω(log(n)),其中 n 是数字,但计算机科学家会说实际上我们要测量运行时间作为一个 length,所以工程师所说的 Ω(log(n)) 实际上是 Ω(k),其中 k 是字节数。不过,正如我之前所说,仅读取一个数字就是 Ω(k),所以我们没有比这更好的方法了。

        【讨论】:

        • 当我们处理 64 位数字时会发生什么?
        • “如果你有足够的内存可以玩”。现在,您不需要存储超过一两个字节的查找表(是的,我知道它在技术上将是 O(log(n)) 而不是 O(1),但随后只需读取您需要的数字O(log(n)))。
        • 我认为您可以使用硬件并行性读取 O(1) 中的数字,但是当您需要对该数字做出决定时,您仍然会遇到 O(log(k))(无论是查找表还是部分求和运算)。
        【解决方案8】:

        下面是两个简单的例子(在 C++ 中),您可以通过这些例子做到这一点。

        1. 我们可以使用 __builtin_popcount() 简单地计算设置位 (1)。

          int numOfOnes(int x) { return __builtin_popcount(x); }

        2. 循环遍历整数中的所有位,检查是否设置了某个位,如果设置了则递增计数变量。

          int hammingDistance(int x) { int count = 0 for(int i = 0; i < 32; i++) if(x & (1 << i)) count++; return count; }

        希望这会有所帮助!

        【讨论】:

          【解决方案9】:

          下面也可以。

          nofone(int x) {
            a=0;
            while(x!=0) {
              x>>=1;
              if(x & 1)
                a++;
            }
            return a;
          } 
          

          【讨论】:

          • 其实这段代码有一个小错误。要看到这一点,只需想象一下 x = 1 会发生什么。
          【解决方案10】:

          以下是使用位运算符的 C 解决方案:

          int numberOfOneBitsInInteger(int input) {
            int numOneBits = 0;
          
            int currNum = input;
            while (currNum != 0) {
              if ((currNum & 1) == 1) {
                numOneBits++;
              }
              currNum = currNum >> 1;
            }
            return numOneBits;
          }
          

          以下是使用 2 的幂的 Java 解决方案:

          public static int numOnesInBinary(int n) {
          
            if (n < 0) return -1;
          
            int j = 0;
            while ( n > Math.pow(2, j)) j++;
          
            int result = 0;
            for (int i=j; i >=0; i--){
              if (n >= Math.pow(2, i)) {
                  n = (int) (n - Math.pow(2,i));
                  result++;    
              }
            }
          
            return result;
          }
          

          【讨论】:

            【解决方案11】:

            该函数接受int 并以二进制表示形式返回“一”的数量

            public static int findOnes(int number)
            {
            
               if(number < 2)
                {
                    if(number == 1)
                    {
                        count ++;
                    }
                    else
                    {
                        return 0;
                    }
                }
            
                value = number % 2;
            
                if(number != 1 && value == 1)
                    count ++;
            
                number /= 2;
            
                findOnes(number);
            
                return count;
            }
            

            【讨论】:

              【解决方案12】:

              在 javascript 中这样做的最佳方式是

              function getBinaryValue(num){
               return num.toString(2);
              }
              
              function checkOnces(binaryValue){
                  return binaryValue.toString().replace(/0/g, "").length;
              }
              

              其中 binaryValue 是二进制字符串,例如:1100

              【讨论】:

                【解决方案13】:

                我能想到的只有一种方法可以在 O(1) 中完成这项任务......那就是“作弊”并使用物理设备(使用线性甚至并行编程,我认为限制是 O(log( k)) 其中k代表数字的字节数)。

                但是,您可以很容易地想象一个物理设备,它将每个位连接到具有 0/1 电压的输出线。然后,您可以通过电子方式读取 O(1) 中“求和”线上的总电压。使用一些基本的电路元件可以很容易地使这个基本思想更加优雅,以您想要的任何形式产生输出(例如二进制编码输出),但基本思想是相同的,电子电路会产生正确的输出固定时间的状态。

                我想也有可能的量子计算,但如果我们被允许这样做,我认为简单的电子电路是更简单的解决方案。

                【讨论】:

                  【解决方案14】:

                  我实际上使用了一些技巧来做到这一点:具有 16 个条目的单个查找表就足够了,您所要做的就是将二进制表示分解成半字节(4 位元组)。复杂度实际上是 O(1),我编写了一个 C++ 模板,它专门用于您想要的整数的大小(以 # 位为单位)……使它成为一个常量表达式,而不是不确定的。

                  fwiw 您可以使用 (i & -i) 将返回 LS 一位并简单地循环,每次剥离 lsbit 直到整数为零的事实 - 但这是一个古老的奇偶校验技巧。

                  【讨论】:

                  • 您的 O(1) 答案假设域是 64 位。
                  • 为什么不使用 16 位查找表?在当今的大多数计算机上,使用 64 KB 内存几乎不会引起注意,并且只需要四次查找来计算 64 位整数的位数。
                  【解决方案15】:

                  我来到这里时坚信我知道解决这个问题的绝妙方法。 C语言代码:

                      short numberOfOnes(unsigned int d) {
                          short count = 0;
                  
                          for (; (d != 0); d &= (d - 1))
                              ++count;
                  
                          return count;
                      }
                  

                  但是在我对这个主题进行了一些研究之后(阅读其他答案:))我发现了 5 种更有效的算法。太爱了!

                  甚至还有专门为此任务设计的 CPU 指令:popcnt。 (在this answer提到)

                  许多算法的描述和基准测试你可以找到here

                  【讨论】:

                    【解决方案16】:

                    下面的方法也可以统计负数中1的个数。

                    private static int countBits(int number)    {
                        int result = 0;
                        while(number != 0)  {
                            result += number & 1;
                            number = number >>> 1;
                        }
                        return result;
                    }
                    

                    但是,像 -1 这样的数字在二进制中表示为 11111111111111111111111111111111,因此需要大量移位。如果你不想为小的负数做这么多的转变,另一种方法可能如下:

                    private static int countBits(int number)    {
                        boolean negFlag = false;
                        if(number < 0)  { 
                            negFlag = true;
                            number = ~number;
                        }
                    
                        int result = 0;
                        while(number != 0)  {
                            result += number & 1;
                            number = number >> 1;
                        }
                        return negFlag? (32-result): result;
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      在 python 或任何其他转换为 bin 字符串中,然后将其与 '0' 拆分以去除 0,然后组合并获取长度。

                      len(''.join(str(bin(122011)).split('0')))-1
                      

                      【讨论】:

                      • 在python中为什么不bin(122011).count("1")
                      【解决方案18】:

                      利用JS的字符串操作可以做到以下几点;

                      0b1111011.toString(2).split(/0|(?=.)/).length // returns 6
                      

                      0b1111011.toString(2).replace("0","").length  // returns 6
                      

                      【讨论】:

                        【解决方案19】:

                        我不得不用红宝石打高尔夫球,结果是

                        l=->x{x.to_s(2).count ?1}
                        

                        用法:

                        l[2**32-1] # returns 32

                        显然效率不高,但可以解决问题:)

                        【讨论】:

                          【解决方案20】:

                          Ruby 实现

                          def find_consecutive_1(n)
                            num = n.to_s(2)
                            arr = num.split("")
                            counter = 0
                            max = 0
                            arr.each do |x|
                                if x.to_i==1
                                    counter +=1
                                else
                                    max = counter if counter > max
                                    counter = 0 
                                end
                                max = counter if counter > max  
                            end
                            max
                          end
                          
                          puts find_consecutive_1(439)
                          

                          【讨论】:

                            【解决方案21】:

                            两种方式:

                            /* Method-1 */
                            int count1s(long num)
                            {
                                int tempCount = 0;
                            
                                while(num)
                                {
                                    tempCount += (num & 1); //inc, based on right most bit checked
                                    num = num >> 1;         //right shift bit by 1
                                }
                            
                                return tempCount;
                            }
                            
                            /* Method-2 */
                            int count1s_(int num)
                            {
                                int tempCount = 0;
                            
                                std::string strNum = std::bitset< 16 >( num ).to_string(); // string conversion
                                cout << "strNum=" << strNum << endl;
                                for(int i=0; i<strNum.size(); i++)
                                {
                                    if('1' == strNum[i])
                                    {
                                        tempCount++;
                                    }
                                }
                            
                                return tempCount;
                            }
                            
                            /* Method-3 (algorithmically - boost string split could be used) */
                            1) split the binary string over '1'.
                            2) count = vector (containing splits) size - 1
                            

                            用法::

                                int count = 0;
                            
                                count = count1s(0b00110011);
                                cout << "count(0b00110011) = " << count << endl; //4
                            
                                count = count1s(0b01110110);
                                cout << "count(0b01110110) = " << count << endl;  //5
                            
                                count = count1s(0b00000000);
                                cout << "count(0b00000000) = " << count << endl;  //0
                            
                                count = count1s(0b11111111);
                                cout << "count(0b11111111) = " << count << endl;  //8
                            
                                count = count1s_(0b1100);
                                cout << "count(0b1100) = " << count << endl;  //2
                            
                                count = count1s_(0b11111111);
                                cout << "count(0b11111111) = " << count << endl;  //8
                            
                                count = count1s_(0b0);
                                cout << "count(0b0) = " << count << endl;  //0
                            
                                count = count1s_(0b1);
                                cout << "count(0b1) = " << count << endl;  //1
                            

                            【讨论】:

                              【解决方案22】:

                              Python 单行代码

                              def countOnes(num):
                                  return bin(num).count('1')
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2016-01-01
                                • 2011-11-10
                                • 2018-07-31
                                • 2011-12-18
                                • 2012-04-17
                                相关资源
                                最近更新 更多