Anzahl der Einsen in binärer Darstellung zählen


75

Effiziente Methode zum Zählen der Anzahl von Einsen in der binären Darstellung einer Zahl in O (1), wenn Sie über genügend Speicher zum Spielen verfügen. Dies ist eine Interviewfrage, die ich in einem Online-Forum gefunden habe, die aber keine Antwort hatte. Kann jemand etwas vorschlagen, ich kann mir keinen Weg vorstellen, es in O (1) Zeit zu tun?


2
Die Frage möchte also, dass Sie betrügen - "genug" Speicher könnte leicht mehr Bits sein, als es Atome im beobachtbaren Universum gibt.
Harold

Wäre es nicht nur ein Array mit der Länge MAX_INT?
Boris Treukhov

1
Bitte korrigieren Sie mich - wenn wir ein Array [0..MAX_INT-1] haben, in dem der Index die tatsächliche Zahl bei der Eingabe ist und die Daten die Anzahl der Einsen für diese Zahl sind (und sagen wir, es wird über den inhaltsadressierbaren Speicher en implementiert .wikipedia.org / wiki / Content-addressable_memory ) wäre es nicht O (1)?
Boris Treukhov

2
Dies ist wahrscheinlich die Modellinterviewlösung, obwohl ich nicht glaube, dass sie einen Puristen zufriedenstellen würde, da sie durch die Datenbreite des adressierbaren Speichers auf der Maschine (z. B. 64 Bit) begrenzt ist. Es würde nicht für Zahlen> 2 ^ 64 funktionieren und wie gesagt, die Frage legt diese Einschränkung nicht fest. Wenn die Frage überarbeitet wurde, um "eine 64-Bit-Zahl" zu sagen, dann ist das eine gute Lösung.
Tim Gee

Andererseits sind für 64-Bit-Zahlen die alten Bitzählmethoden ebenfalls O (1) und verwenden fast keinen Speicher.
Daniel Fischer

Antworten:


56

Das ist das Hamming-Gewichtsproblem , auch bekannt als Bevölkerungszahl. Der Link erwähnt effiziente Implementierungen. Zitat:

Mit unbegrenztem Speicher könnten wir einfach eine große Nachschlagetabelle des Hamming-Gewichts jeder 64-Bit-Ganzzahl erstellen


8
+1 für die korrekte Benennung dieses Problems. Obwohl ich denke, eine vollständige Antwort würde besagen, dass selbst die Verwendung einer Nachschlagetabelle keine O (1) -Zeit wäre, da die Zeit zum Nachschlagen eines Eintrags von der Größe des Eintrags abhängt.
Tim Gee

@TimGee Ein LUT-Zugriff wird immer als O (1) betrachtet. Wenn Sie über unbegrenzten Speicher verfügen, verfügen Sie auch über einen unbegrenzten Adressbus. Egal auf welche Adresse (= Eingabe) Sie zugreifen, es ist immer nur ein Speicherzugriff ;-)
Scolytus

@Scolytus, Referenzen für "Wenn Sie unbegrenzten Speicher haben, haben Sie auch einen unbegrenzten Adressbus."?
Sasha.sochka

1
@ sasha.sochka unbegrenzter Speicher ist ein theoretisches Konstrukt. Es hat überhaupt keinen Adressbus, es ist nur die Annahme, dass Sie immer genug Speicher haben und unabhängig von seiner Größe immer auf die gleiche Weise darauf zugreifen können. In einer realen Implementierung von unbegrenztem Speicher ist Ihr Adressbus also immer> log2 (count (addressable_memory_units)).
Scolytus

1
@ JackAidley wo ist der Link?
Óscar López

48

Ich habe eine Lösung, die die Bits in der O(Number of 1's)Zeit zählt:

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

Im schlimmsten Fall (wenn die Zahl 2 ^ n - 1 ist, sind alle 1 binär) wird jedes Bit überprüft.

Bearbeiten: Habe gerade einen sehr schönen Algorithmus mit konstanter Zeit und konstantem Speicher für Bitcount gefunden. Hier ist es, geschrieben in C:

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

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

Den Beweis für die Richtigkeit finden Sie hier .


Die Idee der Frage ist, die flache Zeit zu verwenden, dh keinen Unterschied in der Laufzeit, ob es null, eins oder viele Einsen gibt.
Vladislav Zorov

1
Die zweite ist nur eine konstante Zeit, da die Größe der Eingabe konstant ist, und in diesem Fall auch die erste. Der erste Algorithmus ist im allgemeinen Fall tatsächlich O (log n), da das bitweise UND und die Subtraktion so lange dauern.
Harold

4
Ihre erste Antwort lautet O (log n), nicht O (1). Ihre zweite Antwort lautet O (1), nimmt jedoch eine Domäne von 32 Bit an (Eingabeparameter ist unsigned int).
Stephen Quan

Eigentlich sollten wir angeben, was n ist. Für Problem 1: Wenn n die Anzahl der Bits in der ursprünglichen Anzahl ist, dann ist dies O (n * Anzahl von 1s), was O (n ^ 2) ist. Wenn n der Wert der Zahl ist, dann ist dieses O (lg ^ 2 n).
John Kurlak

1
Wenn sich einer von Ihnen fragt, was n = n & (n-1) tut, löscht es das niedrigstwertige gesetzte Bit (1) von n. Wenn also n = 0 ist, haben wir innerhalb der Weile die Antwortzeiten wiederholt.
О Г И І І О

18

Bitte beachten Sie, dass: n & (n-1) immer die niedrigstwertige 1 eliminiert.

Daher können wir den Code zur Berechnung der Anzahl der Einsen wie folgt schreiben:

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

Die Komplexität des Programms wäre: Anzahl der Einsen in n (was konstant <32 ist).


16

Ich habe die folgende Lösung von einer anderen Website gesehen:

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 Gute Antwort. Versuchen Sie, eine Erklärung hinzuzufügen. Besonders für die Bitverschiebungsteile in beide Richtungen, was für einige verwirrend sein kann.
Brimborium

1
Erläuterung: A) Wenn das niedrigstwertige Bit 1 ist, erhöhen Sie den Zähler um 1 (so funktioniert dieser Teil: Verschieben nach rechts, dann macht das Rückgängigmachen der Verschiebung das niedrigstwertige Bit auf 0 ... wenn die Differenz zwischen alter und neuer Zahl 1 ist, dann a 1 wurde entfernt) B) Teilen Sie die Zahl durch 2 und nehmen Sie das Wort, indem Sie nach rechts verschieben. 1 C) Wiederholen Sie diesen Vorgang, bis die Zahl 0 ist. Es wäre einfacher, nur UND die Zahl mit 1 zu bestimmen, ob die niedrigstwertige Ziffer eine 1 ist.
John Kurlak

3
Hallo, ich war nur neugierig, wäre das nicht if (orig & 1) count++; orig >>= 1;ein bisschen effizienter?
Herzstück

2
Oder noch besser:count += orig & 1; orig >>= 1;
Noah

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

das ist es?


4

Das wird die kürzeste Antwort in meinem SO-Leben sein: Nachschlagetabelle.

Anscheinend muss ich ein wenig erklären: "Wenn Sie genug Speicher zum Spielen haben" bedeutet, dass wir über den gesamten Speicher verfügen, den wir benötigen (ungeachtet der technischen Möglichkeit). Jetzt müssen Sie die Nachschlagetabelle nicht länger als ein oder zwei Bytes speichern. Während es technisch gesehen eher Ω (log (n)) als O (1) ist, ist das Lesen einer benötigten Zahl Ω (log (n)). Wenn dies also ein Problem ist, ist die Antwort unmöglich - was ist noch kürzer.

Welche von zwei Antworten sie bei einem Interview von Ihnen erwarten, weiß niemand.

Es gibt noch einen weiteren Trick: Während Ingenieure eine Zahl nehmen und über Ω (log (n)) sprechen können, wobei n die Zahl ist, werden Informatiker sagen, dass wir die Laufzeit tatsächlich als Funktion der Länge einer Eingabe messen müssen Das, was Ingenieure Ω (log (n)) nennen, ist tatsächlich Ω (k), wobei k die Anzahl der Bytes ist. Wie ich bereits sagte, ist das Lesen einer Zahl Ω (k). Wir können es also auf keinen Fall besser machen.


Und was passiert, wenn es sich um 64-Bit-Zahlen handelt?
Leppie

1
"Wenn Sie genug Speicher haben, um damit zu spielen" für den Anfang. Jetzt müssen Sie die Nachschlagetabelle nicht länger als ein oder zwei Bytes speichern (ja, ich weiß, dass es technisch gesehen eher O (log (n)) als O (1) ist, sondern nur eine Zahl lesen, die Sie benötigen O (log (n))).
alf

Ich würde denken, Sie können eine Zahl in O (1) mit Hardware-Parallelität lesen, aber Sie bleiben immer noch bei O (log (k)), wenn Sie eine Entscheidung über diese Zahl treffen müssen (sei es eine Nachschlagetabelle oder eine Teilsumme) Operation).
Tim Gee

2

Unten wird auch funktionieren.

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

1
Tatsächlich enthält dieser Code einen geringfügigen Fehler. Um dies zu sehen, stellen Sie sich einfach vor, was für x = 1 passiert.
Andreas Rejbrand

2

Das Folgende ist eine C-Lösung mit Bitoperatoren:

int numberOfOneBitsInInteger(int input) {
  int numOneBits = 0;

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

Das Folgende ist eine Java-Lösung mit Potenzen von 2:

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

Im Folgenden finden Sie zwei einfache Beispiele (in C ++) unter vielen, anhand derer Sie dies tun können.

  1. Wir können einfach gesetzte Bits (Einsen) mit __builtin_popcount () zählen.

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

  2. Durchlaufen Sie alle Bits in einer Ganzzahl, prüfen Sie, ob ein Bit gesetzt ist, und erhöhen Sie dann die Zählvariable.

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

Hoffe das hilft!


1

Die Funktion nimmt ein intund gibt die Anzahl der Einen in binärer Darstellung zurück

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

Der beste Weg in Javascript ist dies

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

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

Dabei ist binaryValue der binäre String, z. B.: 1100


0

Es gibt nur einen Weg, wie ich mir vorstellen kann, diese Aufgabe in O (1) zu erfüllen ... nämlich ein physisches Gerät zu "betrügen" und zu verwenden (bei linearer oder sogar paralleler Programmierung denke ich, dass die Grenze O ist (log (k)). wobei k die Anzahl der Bytes der Anzahl darstellt).

Sie können sich jedoch sehr leicht ein physisches Gerät vorstellen, das jedes Bit und eine Ausgangsleitung mit einer Spannung von 0/1 verbindet. Dann können Sie einfach die Gesamtspannung auf einer Summationsleitung in O (1) elektronisch ablesen. Es wäre ziemlich einfach, diese Grundidee mit einigen grundlegenden Schaltungselementen eleganter zu gestalten, um die Ausgabe in einer beliebigen Form zu erzeugen (z. B. eine binär codierte Ausgabe), aber die wesentliche Idee ist dieselbe und die elektronische Schaltung würde die richtige Ausgabe erzeugen Zustand in fester Zeit.

Ich stelle mir vor, dass es auch Möglichkeiten für Quantencomputer gibt, aber wenn wir das dürfen, würde ich denken, dass eine einfache elektronische Schaltung die einfachere Lösung ist.


0

Ich habe dies tatsächlich mit ein wenig Fingerspitzengefühl getan: Eine einzelne Nachschlagetabelle mit 16 Einträgen wird ausreichen, und alles, was Sie tun müssen, ist, die binäre Wiederholung in Halbbytes (4-Bit-Tupel) zu zerlegen. Die Komplexität ist in der Tat O (1) und ich habe eine C ++ - Vorlage geschrieben, die auf die Größe der gewünschten Ganzzahl (in # Bits) spezialisiert war. Dies macht sie zu einem konstanten Ausdruck anstatt zu einem unbestimmten.

fwiw können Sie die Tatsache nutzen, dass (i & -i) Ihnen das LS-Ein-Bit zurückgibt und einfach eine Schleife abgibt, wobei jedes Mal das lsbit entfernt wird, bis die Ganzzahl Null ist - aber das ist ein alter Paritätstrick.


Ihre O (1) -Antwort geht davon aus, dass die Domain 64-Bit ist.
Stephen Quan

Warum nicht eine 16-Bit-Nachschlagetabelle verwenden? Die Verwendung von 64 KB Speicher würde heutzutage auf den meisten Computern praktisch unbemerkt bleiben und nur vier Suchvorgänge erfordern, um die Bits einer 64-Bit-Ganzzahl zu zählen.
Kef Schecter

0

Ich kam hierher mit dem großen Glauben, dass ich eine schöne Lösung für dieses Problem kenne. Code in C:

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

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

        return count;
    }

Aber nachdem ich ein wenig zu diesem Thema recherchiert habe (andere Antworten lesen :)), habe ich 5 effizientere Algorithmen gefunden. Liebe so!

Es gibt sogar einen CPU-Befehl, der speziell für diese Aufgabe entwickelt wurde : popcnt. (in dieser Antwort erwähnt )

Beschreibung und Benchmarking vieler Algorithmen finden Sie hier .


0

Die folgende Methode kann auch die Anzahl der Einsen in negativen Zahlen zählen.

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

Eine Zahl wie -1 wird jedoch binär als 111111111111111111111111111111 dargestellt und erfordert daher viele Verschiebungen. Wenn Sie nicht so viele Schichten für kleine negative Zahlen machen möchten, könnte ein anderer Weg wie folgt sein:

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

In Python oder einem anderen Konvertierungs-Bin-String teilen Sie ihn dann mit '0', um Nullen zu entfernen. Kombinieren Sie ihn dann und ermitteln Sie die Länge.

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

2
In Python warum nicht bin(122011).count("1")?
Justengel

0

Durch Verwendung von String-Operationen von JS kann man wie folgt vorgehen:

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

oder

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

0

Ich musste dies in Rubin spielen und endete mit

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

Verwendung :

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

Offensichtlich nicht effizient, macht aber den Trick :)


0

Ruby-Implementierung

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

Zwei Wege::

/* 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

Verwendung::

    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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.