以二进制表示形式计数1


77

如果您有足够的内存可玩,一种有效的方法可以对O(1)中数字的二进制表示形式中的1进行计数。这是我在在线论坛上发现的面试问题,但没有答案。有人可以提出建议吗,我想不出一种在O(1)时间内做到的方式?


2
因此,这个问题希望您作弊-“足够多”的内存可能比可观察的宇宙中存在的原子要多。
哈罗德2012年

难道不就是MAX_INT长度的数组吗?
鲍里斯·特鲁霍夫

1
请纠正我-如果我们有一个数组[0..MAX_INT-1],其中索引是输入的实际数字,而数据是该数字的1的数字(并且说它是通过content-addressable memory en实现的.wikipedia.org / wiki / Content-addressable_memory)不是O(1)吗?
鲍里斯·特鲁霍夫

2
那可能是模型访问解决方案,尽管我认为它不能满足纯粹主义者的要求,因为它受到机器上可寻址内存(例如64位)的数据宽度的限制。对于大于2 ^ 64的数字将不起作用,并且如上所述,该问题没有施加此限制。如果将问题修改为说“ 64位数字”,那么是的,这是一个很好的解决方案。
Tim Gee 2012年

另一方面,对于64位数字,旧的位计数方法也是O(1),几乎不使用内存。
丹尼尔·菲舍尔

Answers:


57

那就是汉明(Hamming)体重问题,也就是人口数。该链接提到了有效的实现。报价单:

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


8
+1为正确命名此问题。尽管我认为完整的答案将指出即使使用查找表也不会是O(1)时间,因为查找任何条目的时间取决于条目的大小。
Tim Gee 2012年

@TimGee LUT访问始终被视为O(1)。当您有无限的内存时,您也将具有无限的地址总线。因此,无论您访问哪个地址(=输入),都始终仅是一次内存访问;-)
Scolytus

@Scolytus,是否有“当您拥有无限内存时,您也拥有无限地址总线”的参考。
sasha.sochka's

1
@ sasha.sochka无限内存是一种理论构造。它根本没有地址总线,只是假设您始终有足够的内存,并且始终可以以相同的方式访问它,而与它的大小无关。因此,在无限内存的实际实现中,您的地址总线将始终> log2(count(address(addressable_memory_units))。
Scolytus

1
@JackAidley的链接在哪里?
奥斯卡·洛佩斯

49

我有一个解决方案,它可以计算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;
}

您可以在这里找到其正确性的证明。


这个问题的想法是使用固定时间,即运行时间是否为零,一个或多个1都没有区别。
弗拉迪斯拉夫·佐罗夫(Fladislav Zorov)2012年

1
第二个只是恒定的时间,因为输入的大小是恒定的,在这种情况下,第一个也是。第一种算法实际上是一般情况下的O(log n),这是因为按位与运算和减法运算花费了很长时间。
哈罗德

4
您的第一个答案是O(log n)而不是O(1)。您的第二个答案是O(1),但假定域为32位(输入参数为unsigned int)。
Stephen Quan

实际上,我们应该指定n是什么。对于问题1:如果n是原始数字中的位数,则为O(n * 1s数),即O(n ^ 2)。如果n是数字的值,则此O(lg ^ 2 n)。
John Kurlak

1
如果您想知道n = n&(n-1)的作用,它将清除n的最低有效位(1)。当n = 0时非常有效,我们在while内循环了“ answer”时间。
ΕГИІИО

18

请注意以下事实: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)。


16

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

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;
}

10
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);
}

4
+1好答案。尝试添加一些解释。尤其是对于两个方向上的位移部分,这可能会使某些人感到困惑。
brimborium 2012年

1
说明:A)如果最低有效位是1,则将计数器加1(这是此部分的工作方式:向右移动,然后撤消shift将最低有效位设置为0 ...如果旧数字与新数字之间的差为1,则a 1除去)B)除以数由2和由直到号是0。这将是容易只是AND,以确定与1的数量,如果至少显著位是一个1右移1个C)重复采取地板
约翰Kurlak

3
嗨,我只是好奇,难道不会 if (orig & 1) count++; orig >>= 1;更有效率吗?
Heartinpiece

2
甚至更好:count += orig & 1; orig >>= 1;
诺亚

7
   countBits(x){
     y=0;
     while(x){   
       y += x &  1 ;
       x  = x >> 1 ;
     }
   }

而已?


4

那将是我一生中最短的答案:查找表。

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

他们在面试中期望您回答以下两个问题中的哪一个,没人知道。

还有另一个窍门:虽然工程师可以取一个数字并讨论Ω(log(n)),其中n是数字,但是计算机科学家会说,实际上,我们要根据输入长度的长度来衡量运行时间,因此工程师称Ω(log(n))实际上是Ω(k),其中k是字节数。但是,正如我之前所说,仅读取一个数字就是Ω(k),所以我们没有办法做得更好。


当我们处理64位数字时会发生什么?
嬉皮2012年

1
对于初学者来说,“如果您有足够的内存来玩”。现在,您不需要将查找表存储超过一个或两个字节(是的,我知道从技术上讲它将是O(log(n)),而不是O(1),而只需读取所需的数字O(log(n)))。
2012年

我认为您可以使用硬件并行性在O(1)中读取一个数字,但是当您需要确定该数字(无论是查找表还是部分和)时,您仍然会陷入O(log(k))操作)。
Tim Gee 2012年

2

下面也将工作。

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

1
实际上,此代码中有一个小错误。要看到这一点,只需想象中x = 1发生了什么
安德烈亚斯Rejbrand

2

以下是使用位运算符的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;
}

2

下面是两个简单的示例(在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; }

希望这可以帮助!


1

该函数采用anint并以二进制表示形式返回One的数量

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;
}

1

用javascript的最佳方法是

function getBinaryValue(num){
 return num.toString(2);
}

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

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


0

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

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

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


0

我实际上是用一些技巧来完成此操作的:一个具有16个条目的查找表就足够了,而您要做的就是将二进制rep分成四位(4位元组)。实际上,复杂度是O(1),我写了一个C ++模板,专门针对您想要的整数大小(以#位为单位)…使之成为一个常量表达式,而不是不确定的。

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


您的O(1)答案假定域是64位。
Stephen Quan

为什么不使用16位查找表?今天,大多数计算机几乎不会注意到使用64 KB内存,并且只需要四次查询就可以计算64位整数的位数。
Kef Schecter

0

我来到这里的时候非常有信心,我知道可以很好地解决此问题。C语言代码:

    short numberOfOnes(unsigned int d) {
        short count = 0;

        for (; (d != 0); d &= (d - 1))
            ++count;

        return count;
    }

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

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

您可以在此处找到许多算法的描述和基准测试。


0

以下方法也可以计算负数中的1。

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

但是,像-1这样的数字在二进制中表示为11111111111111111111111111111111111,因此需要进行很多移位。如果您不想为较小的负数进行如此多的移位,则另一种方法可能如下:

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;
}

0

在python或其他任何转换为​​bin字符串中,然后将其用'0'分割以摆脱0,然后合并并获得长度。

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

2
在python中为什么不bin(122011).count("1")呢?
justengel

0

通过利用JS的字符串操作,可以做到以下几点:

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

要么

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

0

我不得不在红宝石上打高尔夫球,最后以

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

用法:

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

显然效率不高,但能达到目的:)


0

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)

0

两种方式:

/* 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.