Erläutern Sie die Verwendung eines Bitvektors, um festzustellen, ob alle Zeichen eindeutig sind


150

Ich bin verwirrt darüber, wie ein Bitvektor dazu funktionieren würde (mit Bitvektoren nicht allzu vertraut). Hier ist der Code angegeben. Könnte mich bitte jemand durch das führen?

public static boolean isUniqueChars(String str) {
    int checker = 0;
    for (int i = 0; i < str.length(); ++i) {
        int val = str.charAt(i) - 'a';
        if ((checker & (1 << val)) > 0) return false;
        checker |= (1 << val);
    }
    return true;
}

Was macht das besonders checker?


Es ist in Java, aber wenn es in C / C ++ etwas Ähnliches gibt, wäre das für mich hilfreicher.
user1136342

101
Dieser Code wurde aus Cracking The Code Interview
Dejell

2
hast du das getestet Es scheint, als würde es keine doppelten '
Riz

3
Beachten Sie, dass die Lösung für niedrigere Zeichen az verwendet wird, was bedeutet, dass wir sie verwenden, um Duplikate für 26 Zeichen zu finden. Hier können also int 32 Bit verwendet werden. Wenn die Reichweite größer gewesen wäre, würde die Lösung nicht funktionieren.
a3.14_Infinity

1
Wenn Leute Fehler machen, verwechseln sie mit der Syntax des Linksverschiebungsoperators - es ist 1, die um x nach links verschoben wird (= str.charAt (i) - 'a'), wobei das Bit von x NICHT um 1 Stelle nach links verschoben wird.
Nanosoft

Antworten:


100

int checkerwird hier als Speicher für Bits verwendet. Jedes Bit im ganzzahligen Wert kann als Flag behandelt werden, also schließlich intals Array von Bits (Flag). Jedes Bit in Ihrem Code gibt an, ob das Zeichen mit dem Bitindex in einer Zeichenfolge gefunden wurde oder nicht. Sie können Bitvektor aus dem gleichen Grund anstelle von verwenden int. Es gibt zwei Unterschiede zwischen ihnen:

  • Größe . inthat eine feste Größe, normalerweise 4 Bytes, was 8 * 4 = 32 Bits (Flags) bedeutet. Bitvektoren können normalerweise unterschiedlich groß sein, oder Sie sollten die Größe im Konstruktor angeben.

  • API . Mit Bitvektoren können Sie Code leichter lesen, wahrscheinlich ungefähr so:

    vector.SetFlag(4, true); // set flag at index 4 as true

    für intSie auf niedrigerer Ebene Bit Logik - Code haben:

    checker |= (1 << 5); // set flag at index 5 to true

Auch wahrscheinlich int kann ein wenig schneller sein, weil Operationen mit Bits sehr niedriges Niveau sind und ausgeführt werden , wie sie ist durch die CPU. Mit BitVector können Sie stattdessen etwas weniger kryptischen Code schreiben und mehr Flags speichern.

Zum späteren Nachschlagen: Der Bitvektor wird auch als bitSet oder bitArray bezeichnet. Hier einige Links zu dieser Datenstruktur für verschiedene Sprachen / Plattformen:


Hat Java eine BitVector-Klasse? Ich konnte keine Dokumentation dazu finden!
Dejell

Die Größe hat eine feste Größe, die 32 Bit beträgt. Bedeutet das, dass nur 32 eindeutige Zeichen getestet werden können? Ich habe getestet, dass diese Funktion testen könnte, "abcdefgZZ" ist falsch, aber "abcdefg @@" gibt true zurück.
tli2020

1
Google hat mich hierher geführt. @Dejel Hier ist die Java-Datenstruktur, die Sie verwenden können: docs.oracle.com/javase/7/docs/api/java/util/BitSet.html . Hoffentlich hilft dies jemandem, der durch die Zwischenrohre reist.
Nattyddubbs

@nattyddubbs, danke, ich habe diesen und mehrere andere Links zur Antwort
hinzugefügt

222

Ich habe den Verdacht, dass Sie diesen Code aus demselben Buch erhalten haben, das ich gerade lese ... Der Code selbst hier ist bei weitem nicht so kryptisch wie die Operatoren- | =, & und <<, die normalerweise nicht verwendet werden Wir Laien - der Autor hat sich nicht die Mühe gemacht, sich die zusätzliche Zeit zu nehmen, um den Prozess zu erklären, noch was die eigentliche Mechanik hier ist. Ich war am Anfang mit der vorherigen Antwort auf diesen Thread zufrieden, aber nur auf einer abstrakten Ebene. Ich bin darauf zurückgekommen, weil ich der Meinung war, dass es eine konkretere Erklärung geben muss - das Fehlen einer Erklärung hinterlässt bei mir immer ein unangenehmes Gefühl.

Dieser Operator << ist ein linker bitweiser Shifter. Er nimmt die binäre Darstellung dieser Zahl oder dieses Operanden und verschiebt sie über so viele Stellen, die durch den Operanden oder die Zahl auf der rechten Seite angegeben sind, wie in Dezimalzahlen nur in Binärdateien. Wir multiplizieren mit der Basis 2 - wenn wir uns nach oben bewegen, jedoch an vielen Stellen, die nicht die Basis 10 sind -, ist die Zahl rechts der Exponent und die Zahl links ein Basismultiplikator von 2.

Dieser Operator | = nimmt den Operanden links und oder ist es mit dem Operanden rechts - und diesem - '&' und die Bits beider Operanden links und rechts davon.

Wir haben hier also eine Hash-Tabelle, die jedes Mal in einer 32-Bit-Binärzahl gespeichert wird, wenn der Prüfer or'd ( checker |= (1 << val)) mit dem angegebenen Binärwert eines Buchstabens erhält, dessen entsprechendes Bit auf true gesetzt wird. Der Wert des Zeichens ist und wird mit dem checker ( checker & (1 << val)) > 0) angegeben - wenn er größer als 0 ist, wissen wir, dass wir einen Betrüger haben -, weil zwei identische Bits, die auf true gesetzt und zusammen gesetzt sind, true oder '1' 'zurückgeben.

Es gibt 26 binäre Stellen, von denen jede einem Kleinbuchstaben entspricht - der Autor hat angenommen, dass die Zeichenfolge nur Kleinbuchstaben enthält -, und dies liegt daran, dass wir nur noch 6 weitere Stellen (in 32-Bit-Ganzzahlen) verbrauchen müssen - und dann eine Kollision bekommen

00000000000000000000000000000001 a 2^0

00000000000000000000000000000010 b 2^1

00000000000000000000000000000100 c 2^2

00000000000000000000000000001000 d 2^3

00000000000000000000000000010000 e 2^4

00000000000000000000000000100000 f 2^5

00000000000000000000000001000000 g 2^6

00000000000000000000000010000000 h 2^7

00000000000000000000000100000000 i 2^8

00000000000000000000001000000000 j 2^9

00000000000000000000010000000000 k 2^10

00000000000000000000100000000000 l 2^11

00000000000000000001000000000000 m 2^12

00000000000000000010000000000000 n 2^13

00000000000000000100000000000000 o 2^14

00000000000000001000000000000000 p 2^15

00000000000000010000000000000000 q 2^16

00000000000000100000000000000000 r 2^17

00000000000001000000000000000000 s 2^18

00000000000010000000000000000000 t 2^19

00000000000100000000000000000000 u 2^20

00000000001000000000000000000000 v 2^21

00000000010000000000000000000000 w 2^22

00000000100000000000000000000000 x 2^23

00000001000000000000000000000000 y 2^24

00000010000000000000000000000000 z 2^25

Also, für eine Eingabezeichenfolge 'azya', während wir uns Schritt für Schritt bewegen

Zeichenfolge 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

checker='a' or checker;
// checker now becomes = 00000000000000000000000000000001
checker=00000000000000000000000000000001

a and checker=0 no dupes condition

Zeichenfolge 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

checker=z or checker;
// checker now becomes 00000010000000000000000000000001  

Zeichenfolge 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

checker= checker or y;
// checker now becomes = 00000011000000000000000000000001

Zeichenfolge 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Jetzt wird ein Duplikat deklariert


@ ivan-tichy hast du das getestet? Es scheint, als würde es keine doppelten 'a'-Zeichen erkennen, da es auf 0 gesetzt ist und bei Linksverschiebung immer noch bei 0 bleibt.
Riz

1
@Riz Nein, es beginnt immer mit '1', der Algorithmus verschiebt 1 basierend auf dem Buchstaben. Wenn also der Buchstabe 'a' einmal kommt, ist es 1, was (.... 000001) ist.
Taylor Halliday

2
@ Ivan Man, ich habe das Gleiche gedacht. Selbst die ausgewählte Antwort erklärte nichts über die Operatoren. Vielen Dank für die detaillierten Infos.
WowBow

Sollte ich annehmen, dass die oben genannte eindeutige Prüfung nur mit sortiertem Zeichensatz (abcd ... z) funktioniert? nicht mit (bcad ...)
abdul rashid

"Ich habe den Verdacht, dass Sie diesen Code aus demselben Buch haben, das ich gerade lese." Dasselbe hier :) brachte mich zum Lachen
Rückgrat

39

Ich denke, all diese Antworten erklären, wie das funktioniert, aber ich wollte meinen Beitrag dazu leisten, wie ich es besser gesehen habe, indem ich einige Variablen umbenannte, andere hinzufügte und Kommentare hinzufügte:

public static boolean isUniqueChars(String str) {

    /*
    checker is the bit array, it will have a 1 on the character index that
    has appeared before and a 0 if the character has not appeared, you
    can see this number initialized as 32 0 bits:
    00000000 00000000 00000000 00000000
     */
    int checker = 0;

    //loop through each String character
    for (int i = 0; i < str.length(); ++i) {
        /*
        a through z in ASCII are charactets numbered 97 through 122, 26 characters total
        with this, you get a number between 0 and 25 to represent each character index
        0 for 'a' and 25 for 'z'

        renamed 'val' as 'characterIndex' to be more descriptive
         */
        int characterIndex = str.charAt(i) - 'a'; //char 'a' would get 0 and char 'z' would get 26

        /*
        created a new variable to make things clearer 'singleBitOnPosition'

        It is used to calculate a number that represents the bit value of having that 
        character index as a 1 and the rest as a 0, this is achieved
        by getting the single digit 1 and shifting it to the left as many
        times as the character index requires
        e.g. character 'd'
        00000000 00000000 00000000 00000001
        Shift 3 spaces to the left (<<) because 'd' index is number 3
        1 shift: 00000000 00000000 00000000 00000010
        2 shift: 00000000 00000000 00000000 00000100
        3 shift: 00000000 00000000 00000000 00001000

        Therefore the number representing 'd' is
        00000000 00000000 00000000 00001000

         */
        int singleBitOnPosition = 1 << characterIndex;

        /*
        This peforms an AND between the checker, which is the bit array
        containing everything that has been found before and the number
        representing the bit that will be turned on for this particular
        character. e.g.
        if we have already seen 'a', 'b' and 'd', checker will have:
        checker = 00000000 00000000 00000000 00001011
        And if we see 'b' again:
        'b' = 00000000 00000000 00000000 00000010

        it will do the following:
        00000000 00000000 00000000 00001011
        & (AND)
        00000000 00000000 00000000 00000010
        -----------------------------------
        00000000 00000000 00000000 00000010

        Since this number is different than '0' it means that the character
        was seen before, because on that character index we already have a 
        1 bit value
         */
        if ((checker & singleBitOnPosition) > 0) {
            return false;
        }

        /* 
        Remember that 
        checker |= singleBitOnPosition is the same as  
        checker = checker | singleBitOnPosition
        Sometimes it is easier to see it expanded like that.

        What this achieves is that it builds the checker to have the new 
        value it hasnt seen, by doing an OR between checker and the value 
        representing this character index as a 1. e.g.
        If the character is 'f' and the checker has seen 'g' and 'a', the 
        following will happen

        'f' = 00000000 00000000 00000000 00100000
        checker(seen 'a' and 'g' so far) = 00000000 00000000 00000000 01000001

        00000000 00000000 00000000 00100000
        | (OR)
        00000000 00000000 00000000 01000001
        -----------------------------------
        00000000 00000000 00000000 01100001

        Therefore getting a new checker as 00000000 00000000 00000000 01100001

         */
        checker |= singleBitOnPosition;
    }
    return true;
}

2
Tolle Erklärung. Danke!
Hormigas

Klare Erklärung.
Danke

Tolle Erklärung. Einfach zu verstehen. Vielen Dank
Anil Kumar

Das ist das Beste
Vladimir Nabokov

Dies ist genau der Grund, warum Kommentare erfunden wurden.
Herr Suryaa Jha

30

Ich gehe auch davon aus, dass Ihr Beispiel aus dem Buch Cracking The Code Interview stammt und meine Antwort sich auf diesen Kontext bezieht.

Um diesen Algorithmus zur Lösung des Problems verwenden zu können, müssen wir zugeben, dass wir nur Zeichen von a nach z (Kleinbuchstaben) übergeben.

Da es nur 26 Buchstaben gibt und diese in der von uns verwendeten Codierungstabelle richtig sortiert sind, garantiert dies, dass alle potenziellen Unterschiede str.charAt(i) - 'a'unter 32 liegen (die Größe der int-Variablen checker).

Wie von Snowbear erklärt, werden wir die checkerVariable als Array von Bits verwenden. Lassen Sie uns einen beispielhaften Ansatz verfolgen:

Sagen wir str equals "test"

  • Erster Durchgang (i = t)

checker == 0 (00000000000000000000000000000000)

In ASCII, val = str.charAt(i) - 'a' = 116 - 97 = 19
What about 1 << val ?
1          == 00000000000000000000000000000001
1 << 19    == 00000000000010000000000000000000
checker |= (1 << val) means checker = checker | (1 << val)
so checker = 00000000000000000000000000000000 | 00000000000010000000000000000000
checker == 524288 (00000000000010000000000000000000)
  • Zweiter Durchgang (i = e)

checker == 524288 (00000000000010000000000000000000)

val = 101 - 97 = 4
1          == 00000000000000000000000000000001
1 << 4     == 00000000000000000000000000010000
checker |= (1 << val) 
so checker = 00000000000010000000000000000000 | 00000000000000000000000000010000
checker == 524304 (00000000000010000000000000010000)

und so weiter ... bis wir über die Bedingung ein bereits gesetztes Bit im Checker für ein bestimmtes Zeichen finden

(checker & (1 << val)) > 0

Ich hoffe es hilft


2
Viel bessere Erklärung als der Rest IMO, aber eine Sache, die ich immer noch nicht bekomme, ist checker = 00000000000010000000000000000000 | 00000000000000000000000000010000 ist nicht der bitweise | = ODER-Operator. würde das nicht den einen oder anderen Wert seitdem auswählen? Warum werden beide Bits verwendet und gesetzt?
CodeCrack

@ CodeCrack du hast gesagt, es ist bitweise ODER. Es wird auf Bitebene und nicht auf Bit-Array-Ebene verglichen. Hinweis: int ist Bit Array
MusicMan

7

Es gibt bereits einige ausgezeichnete Antworten, die oben angegeben wurden. Ich möchte also nicht wiederholen, was alles bereits gesagt hat. Ich wollte jedoch einige Dinge hinzufügen, um das obige Programm zu unterstützen, da ich gerade dasselbe Programm durchgearbeitet habe und einige Fragen hatte, aber nachdem ich einige Zeit damit verbracht habe, habe ich mehr Klarheit über dieses Programm.

Zunächst wird "checker" verwendet, um das Zeichen zu verfolgen, das bereits in der Zeichenfolge durchlaufen wird, um festzustellen, ob Zeichen wiederholt werden.

Jetzt ist "checker" ein int-Datentyp, der nur 32 Bit oder 4 Byte (je nach Plattform) enthalten kann, sodass dieses Programm nur für einen Zeichensatz innerhalb eines Bereichs von 32 Zeichen korrekt funktioniert. Aus diesem Grund subtrahiert dieses Programm 'a' von jedem Zeichen, damit dieses Programm nur für Kleinbuchstaben ausgeführt wird. Wenn Sie jedoch Klein- und Großbuchstaben mischen, funktioniert dies nicht.

Übrigens, wenn Sie nicht 'a' von jedem Zeichen subtrahieren (siehe folgende Anweisung), funktioniert dieses Programm nur für Zeichenfolgen mit Großbuchstaben oder Zeichenfolgen mit nur Kleinbuchstaben korrekt. Der Umfang des obigen Programms erhöht sich also von nur Kleinbuchstaben auf Großbuchstaben, aber sie können nicht miteinander gemischt werden.

int val = str.charAt(i) - 'a'; 

Ich wollte jedoch ein generisches Programm mit Bitwise Operation schreiben, das für alle ASCII-Zeichen funktionieren sollte, ohne sich um Groß-, Klein-, Zahlen oder Sonderzeichen kümmern zu müssen. Zu diesem Zweck sollte unser "Checker" groß genug sein, um 256 Zeichen (ASCII-Zeichensatzgröße) zu speichern. Ein int in Java würde jedoch nicht funktionieren, da es nur 32 Bit speichern kann. Daher verwende ich im folgenden Programm die in JDK verfügbare BitSet-Klasse, bei der beim Instanziieren eines BitSet-Objekts eine beliebige benutzerdefinierte Größe übergeben werden kann.

Hier ist ein Programm, das dasselbe tut wie das oben beschriebene Programm, das mit dem Bitwise-Operator geschrieben wurde, aber dieses Programm funktioniert für einen String mit einem beliebigen Zeichen aus dem ASCII-Zeichensatz.

public static boolean isUniqueStringUsingBitVectorClass(String s) {

    final int ASCII_CHARACTER_SET_SIZE = 256;

    final BitSet tracker = new BitSet(ASCII_CHARACTER_SET_SIZE);

    // if more than  256 ASCII characters then there can't be unique characters
    if(s.length() > 256) {
        return false;
    }

    //this will be used to keep the location of each character in String
    final BitSet charBitLocation = new BitSet(ASCII_CHARACTER_SET_SIZE);

    for(int i = 0; i < s.length(); i++) {

        int charVal = s.charAt(i);
        charBitLocation.set(charVal); //set the char location in BitSet

        //check if tracker has already bit set with the bit present in charBitLocation
        if(tracker.intersects(charBitLocation)) {
            return false;
        }

        //set the tracker with new bit from charBitLocation
        tracker.or(charBitLocation);

        charBitLocation.clear(); //clear charBitLocation to store bit for character in the next iteration of the loop

    }

    return true;

}

1
Ich habe nach dieser Lösung gesucht, es sind jedoch keine zwei BitSet-Variablen erforderlich. Nur der Tracker ist genug. Aktualisiert für Schleifencode: for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
Zambro

7

Das Lesen von Iwans Antwort oben hat mir wirklich geholfen, obwohl ich es etwas anders formulieren würde.

Das <<In (1 << val)ist ein etwas verschiebender Operator. Es nimmt 1(was in Binärform dargestellt wird 000000001, mit so vielen vorhergehenden Nullen, wie Sie möchten / vom Speicher zugewiesen werden) und verschiebt es durch valLeerzeichen nach links . Da wir nur az annehmen und ajedes Mal subtrahieren , hat jeder Buchstabe einen Wert von 0-25. Dies ist der Index dieses Buchstabens von rechts in der checkerbooleschen Darstellung der Ganzzahl, da wir 1den checker valWert in Zeiten nach links verschieben .

Am Ende jeder Prüfung sehen wir den |=Bediener. Dadurch werden zwei Binärzahlen zusammengeführt, wobei alle durch 0s ersetzt werden 1, wenn 1in einem der Operanden an diesem Index a vorhanden ist. Hier bedeutet dies, dass überall dort, wo eine 1existiert (1 << val), 1diese kopiert wird checker, während alle checkervorhandenen Einsen erhalten bleiben.

Wie Sie wahrscheinlich erraten können, 1fungiert a hier als boolesches Flag für true. Wenn wir prüfen, ob ein Zeichen bereits in der Zeichenfolge dargestellt ist, vergleichen wir checker, was zu diesem Zeitpunkt im Wesentlichen ein Array von Booleschen Flags ( 1Werten) an den Indizes der bereits dargestellten Zeichen ist, mit dem, was im Wesentlichen ein Array von ist Boolesche Werte mit einem 1Flag am Index des aktuellen Zeichens.

Der &Bediener führt diese Prüfung durch. Ähnlich wie bei kopiert |=der &Operator 1 nur dann über a , wenn beide Operanden einen 1an diesem Index haben. Im Wesentlichen werden also nur Flags kopiert, die bereits vorhanden sind und in checkerdenen auch dargestellt sind (1 << val). In diesem Fall bedeutet dies, dass nur dann, wenn das aktuelle Zeichen bereits dargestellt wurde, 1irgendwo im Ergebnis von ein Geschenk vorhanden ist checker & (1 << val). Und wenn 1irgendwo im Ergebnis dieser Operation a vorhanden ist, ist der Wert des zurückgegebenen Booleschen Werts > 0und die Methode gibt false zurück.

Ich vermute, deshalb werden Bitvektoren auch als Bit-Arrays bezeichnet . Obwohl sie nicht vom Array-Datentyp sind, können sie ähnlich wie Arrays zum Speichern von Booleschen Flags verwendet werden.


1
Sehr hilfreich, danke für deine Java-Info-Streusel.
Bachiri Taoufiq Abderrahman

4

Einfache Erklärung (mit JS-Code unten)

  • Eine ganzzahlige Variable pro Maschinencode ist ein 32-Bit-Array
  • Alle bitweisen Operationen sind 32-bit
  • Sie sind unabhängig von der OS / CPU-Architektur oder dem gewählten Zahlensystem der Sprache, z DEC64. B. für JS.
  • Dieser Ansatz zum Auffinden von Duplikaten ähnelt dem Speichern von Zeichen in einem Array der Größe 32, wobei wir den 0thIndex festlegen , wenn wir ihn ain der Zeichenfolge finden, 1stfür busw.
  • Bei einem doppelten Zeichen in der Zeichenfolge wird das entsprechende Bit belegt oder in diesem Fall auf 1 gesetzt.
  • Ivan hat bereits erklärt : Wie diese Indexberechnung in dieser vorherigen Antwort funktioniert .

Zusammenfassung der Operationen:

  • Führen Sie eine UND- Verknüpfung zwischen checker& indexdes Zeichens aus
  • Intern sind beide Int-32-Arrays
  • Es ist eine bitweise Operation zwischen diesen 2.
  • Überprüfen Sie ifdie Ausgabe der Operation war1
  • wenn output == 1
    • Für die checkerVariable ist in beiden Arrays das bestimmte indexierte Bit gesetzt
    • Somit ist es ein Duplikat.
  • wenn output == 0
    • Dieser Charakter wurde bisher nicht gefunden
    • Führen Sie eine ODER- Verknüpfung zwischen checker& indexdes Zeichens aus
    • Dadurch wird das indexierte Bit auf aktualisiert 1
    • Weisen Sie die Ausgabe zu checker

Annahmen:

  • Wir haben angenommen, dass wir alle Kleinbuchstaben erhalten
  • Und diese Größe 32 ist genug
  • Daher haben wir unsere Indexzählung ab 96 als Referenzpunkt unter Berücksichtigung des ASCII- Codes für ais begonnen97

Unten ist der JavaScript- Quellcode angegeben.

function checkIfUniqueChars (str) {

    var checker = 0; // 32 or 64 bit integer variable 

    for (var i = 0; i< str.length; i++) {
        var index = str[i].charCodeAt(0) - 96;
        var bitRepresentationOfIndex = 1 << index;

        if ( (checker & bitRepresentationOfIndex) > 1) {
            console.log(str, false);
            return false;
        } else {
            checker = (checker | bitRepresentationOfIndex);
        }
    }
    console.log(str, true);
    return true;
}

checkIfUniqueChars("abcdefghi");  // true
checkIfUniqueChars("aabcdefghi"); // false
checkIfUniqueChars("abbcdefghi"); // false
checkIfUniqueChars("abcdefghii"); // false
checkIfUniqueChars("abcdefghii"); // false

Beachten Sie, dass in JS trotz Ganzzahlen von 64 Bit immer eine bitweise Operation für 32 Bit ausgeführt wird.

Beispiel: Wenn die Zeichenfolge lautet, aadann:

// checker is intialized to 32-bit-Int(0)
// therefore, checker is
checker= 00000000000000000000000000000000

i = 0

str[0] is 'a'
str[i].charCodeAt(0) - 96 = 1

checker 'AND' 32-bit-Int(1) = 00000000000000000000000000000000
Boolean(0) == false

// So, we go for the '`OR`' operation.

checker = checker OR 32-bit-Int(1)
checker = 00000000000000000000000000000001

i = 1

str[1] is 'a'
str[i].charCodeAt(0) - 96 = 1

checker= 00000000000000000000000000000001
a      = 00000000000000000000000000000001

checker 'AND' 32-bit-Int(1) = 00000000000000000000000000000001
Boolean(1) == true
// We've our duplicate now

3

Lassen Sie uns den Code Zeile für Zeile aufschlüsseln.

int checker = 0; Wir initiieren einen Checker, der uns hilft, doppelte Werte zu finden.

int val = str.charAt (i) - 'a'; Wir erhalten den ASCII-Wert des Zeichens an der i-ten Position der Zeichenfolge und subtrahieren ihn mit dem ASCII-Wert von 'a'. Da davon ausgegangen wird, dass die Zeichenfolge nur aus niedrigeren Zeichen besteht, ist die Anzahl der Zeichen auf 26 begrenzt. Hece, der Wert von 'val' ist immer> = 0.

if ((checker & (1 << val))> 0) gibt false zurück;

checker | = (1 << val);

Das ist der schwierige Teil. Betrachten wir ein Beispiel mit der Zeichenfolge "abcda". Dies sollte idealerweise false zurückgeben.

Für Schleifeniteration 1:

Prüfer: 00000000000000000000000000000000

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

checker & (1 << val): 0000000000000000000000000000000000 ist nicht> 0

Daher Prüfer: 0000000000000000000000000000000001

Für Schleifeniteration 2:

Prüfer: 00000000000000000000000000000001

val: 98-97 = 1

1 << 0: 00000000000000000000000000000010

checker & (1 << val): 0000000000000000000000000000000000 ist nicht> 0

Daher Prüfer: 00000000000000000000000000000011

Für Schleifeniteration 3:

Prüfer: 00000000000000000000000000000011

val: 99-97 = 0

1 << 0: 0000000000000000000000000000000100

checker & (1 << val): 0000000000000000000000000000000000 ist nicht> 0

Daher Prüfer: 00000000000000000000000000000111

Für die Schleifeniteration 4:

Prüfer: 00000000000000000000000000000111

val: 100-97 = 0

1 << 0: 0000000000000000000000000000001000

checker & (1 << val): 0000000000000000000000000000000000 ist nicht> 0

Daher Prüfer: 00000000000000000000000000001111

Für die Schleifeniteration 5:

Prüfer: 00000000000000000000000000001111

val: 97-97 = 0

1 << 0: 00000000000000000000000000000001

checker & (1 << val): 00000000000000000000000000000001 ist> 0

Geben Sie daher false zurück.


val: 99-97 = 0 sollte val sein: 99-97 = 2 und val: 100-97 = 0 sollte 3 sein
Brosef

2
public static void main (String[] args)
{
    //In order to understand this algorithm, it is necessary to understand the following:

    //int checker = 0;
    //Here we are using the primitive int almost like an array of size 32 where the only values can be 1 or 0
    //Since in Java, we have 4 bytes per int, 8 bits per byte, we have a total of 4x8=32 bits to work with

    //int val = str.charAt(i) - 'a';
    //In order to understand what is going on here, we must realize that all characters have a numeric value
    for (int i = 0; i < 256; i++)
    {
        char val = (char)i;
        System.out.print(val);
    }

    //The output is something like:
    //             !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
    //There seems to be ~15 leading spaces that do not copy paste well, so I had to use real spaces instead

    //To only print the characters from 'a' on forward:
    System.out.println();
    System.out.println();

    for (int i=0; i < 256; i++)
    {
        char val = (char)i;
        //char val2 = val + 'a'; //incompatible types. required: char found: int
        int val2 = val + 'a';  //shift to the 'a', we must use an int here otherwise the compiler will complain
        char val3 = (char)val2;  //convert back to char. there should be a more elegant way of doing this.
        System.out.print(val3);
    }

    //Notice how the following does not work:
    System.out.println();
    System.out.println();

    for (int i=0; i < 256; i++)
    {
        char val = (char)i;
        int val2 = val - 'a';
        char val3 = (char)val2;
        System.out.print(val3);
    }
    //I'm not sure why this spills out into 2 lines:
    //EDIT I cant seem to copy this into stackoverflow!

    System.out.println();
    System.out.println();

    //So back to our original algorithm:
    //int val = str.charAt(i) - 'a';
    //We convert the i'th character of the String to a character, and shift it to the right, since adding shifts to the right and subtracting shifts to the left it seems

    //if ((checker & (1 << val)) > 0) return false;
    //This line is quite a mouthful, lets break it down:
    System.out.println(0<<0);
    //00000000000000000000000000000000
    System.out.println(0<<1);
    //00000000000000000000000000000000
    System.out.println(0<<2);
    //00000000000000000000000000000000
    System.out.println(0<<3);
    //00000000000000000000000000000000
    System.out.println(1<<0);
    //00000000000000000000000000000001
    System.out.println(1<<1);
    //00000000000000000000000000000010 == 2
    System.out.println(1<<2);
    //00000000000000000000000000000100 == 4
    System.out.println(1<<3);
    //00000000000000000000000000001000 == 8
    System.out.println(2<<0);
    //00000000000000000000000000000010 == 2
    System.out.println(2<<1);
    //00000000000000000000000000000100 == 4
    System.out.println(2<<2);
    // == 8
    System.out.println(2<<3);
    // == 16
    System.out.println("3<<0 == "+(3<<0));
    // != 4 why 3???
    System.out.println(3<<1);
    //00000000000000000000000000000011 == 3
    //shift left by 1
    //00000000000000000000000000000110 == 6
    System.out.println(3<<2);
    //00000000000000000000000000000011 == 3
    //shift left by 2
    //00000000000000000000000000001100 == 12
    System.out.println(3<<3);
    // 24

    //It seems that the -  'a' is not necessary
    //Back to if ((checker & (1 << val)) > 0) return false;
    //(1 << val means we simply shift 1 by the numeric representation of the current character
    //the bitwise & works as such:
    System.out.println();
    System.out.println();
    System.out.println(0&0);    //0
    System.out.println(0&1);       //0
    System.out.println(0&2);          //0
    System.out.println();
    System.out.println();
    System.out.println(1&0);    //0
    System.out.println(1&1);       //1
    System.out.println(1&2);          //0
    System.out.println(1&3);             //1
    System.out.println();
    System.out.println();
    System.out.println(2&0);    //0
    System.out.println(2&1);       //0   0010 & 0001 == 0000 = 0
    System.out.println(2&2);          //2  0010 & 0010 == 2
    System.out.println(2&3);             //2  0010 & 0011 = 0010 == 2
    System.out.println();
    System.out.println();
    System.out.println(3&0);    //0    0011 & 0000 == 0
    System.out.println(3&1);       //1  0011 & 0001 == 0001 == 1
    System.out.println(3&2);          //2  0011 & 0010 == 0010 == 2, 0&1 = 0 1&1 = 1
    System.out.println(3&3);             //3 why?? 3 == 0011 & 0011 == 3???
    System.out.println(9&11);   // should be... 1001 & 1011 == 1001 == 8+1 == 9?? yay!

    //so when we do (1 << val), we take 0001 and shift it by say, 97 for 'a', since any 'a' is also 97

    //why is it that the result of bitwise & is > 0 means its a dupe?
    //lets see..

    //0011 & 0011 is 0011 means its a dupe
    //0000 & 0011 is 0000 means no dupe
    //0010 & 0001 is 0011 means its no dupe
    //hmm
    //only when it is all 0000 means its no dupe

    //so moving on:
    //checker |= (1 << val)
    //the |= needs exploring:

    int x = 0;
    int y = 1;
    int z = 2;
    int a = 3;
    int b = 4;
    System.out.println("x|=1 "+(x|=1));  //1
    System.out.println(x|=1);     //1
    System.out.println(x|=1);      //1
    System.out.println(x|=1);       //1
    System.out.println(x|=1);       //1
    System.out.println(y|=1); // 0001 |= 0001 == ?? 1????
    System.out.println(y|=2); // ??? == 3 why??? 0001 |= 0010 == 3... hmm
    System.out.println(y);  //should be 3?? 
    System.out.println(y|=1); //already 3 so... 0011 |= 0001... maybe 0011 again? 3?
    System.out.println(y|=2); //0011 |= 0010..... hmm maybe.. 0011??? still 3? yup!
    System.out.println(y|=3); //0011 |= 0011, still 3
    System.out.println(y|=4);  //0011 |= 0100.. should be... 0111? so... 11? no its 7
    System.out.println(y|=5);  //so we're at 7 which is 0111, 0111 |= 0101 means 0111 still 7
    System.out.println(b|=9); //so 0100 |= 1001 is... seems like xor?? or just or i think, just or... so its 1101 so its 13? YAY!

    //so the |= is just a bitwise OR!
}

public static boolean isUniqueChars(String str) {
    int checker = 0;
    for (int i = 0; i < str.length(); ++i) {
        int val = str.charAt(i) - 'a';  //the - 'a' is just smoke and mirrors! not necessary!
        if ((checker & (1 << val)) > 0) return false;
        checker |= (1 << val);
    }
    return true;
}

public static boolean is_unique(String input)
{
    int using_int_as_32_flags = 0;
    for (int i=0; i < input.length(); i++)
    {
        int numeric_representation_of_char_at_i = input.charAt(i);
        int using_0001_and_shifting_it_by_the_numeric_representation = 1 << numeric_representation_of_char_at_i; //here we shift the bitwise representation of 1 by the numeric val of the character
        int result_of_bitwise_and = using_int_as_32_flags & using_0001_and_shifting_it_by_the_numeric_representation;
        boolean already_bit_flagged = result_of_bitwise_and > 0;              //needs clarification why is it that the result of bitwise & is > 0 means its a dupe?
        if (already_bit_flagged)
            return false;
        using_int_as_32_flags |= using_0001_and_shifting_it_by_the_numeric_representation;
    }
    return true;
}

0

In den vorherigen Beiträgen wurde gut erklärt, was der Codeblock tut, und ich möchte meine einfache Lösung mithilfe der BitSet-Java-Datenstruktur hinzufügen:

private static String isUniqueCharsUsingBitSet(String string) {
  BitSet bitSet =new BitSet();
    for (int i = 0; i < string.length(); ++i) {
        int val = string.charAt(i);
        if(bitSet.get(val)) return "NO";
        bitSet.set(val);
    }
  return "YES";
}

0
Line 1:   public static boolean isUniqueChars(String str) {
Line 2:      int checker = 0;
Line 3:      for (int i = 0; i < str.length(); ++i) {
Line 4:          int val = str.charAt(i) - 'a';
Line 5:          if ((checker & (1 << val)) > 0) return false;
Line 6:         checker |= (1 << val);
Line 7:      }
Line 8:      return true;
Line 9:   }

So wie ich es mit Javascript verstanden habe. Eingabe annehmenvar inputChar = "abca"; //find if inputChar has all unique characters

Lasst uns beginnen

Line 4: int val = str.charAt(i) - 'a';

Obere Zeile Findet den Binärwert des ersten Zeichens in inputChar, der a , a = 97 in ASCII ist, und konvertiert dann 97 in Binär zu 1100001 .

In Javascript "a".charCodeAt().toString(2) gibt zB : 1100001 zurück

checker = 0 // binäre 32-Bit-Darstellung = 0000000000000000000000000

checker = 1100001 | checker; // Prüfer wird zu 1100001 (In 32-Bit-Darstellung wird es zu 000000000 ..... 00001100001)

Aber ich möchte, dass meine Bitmaske ( int checker) nur ein Bit setzt, aber der Checker ist 1100001

Line 4:          int val = str.charAt(i) - 'a';

Jetzt ist der obige Code praktisch. Ich subtrahiere nur immer 97 (ASCII-Wert von a)

val = 0; // 97 - 97  Which is  a - a
val = 1; // 98 - 97 Which is b - a
val = 1;  // 99 - 97 Which is c - a

Lasst valuns verwenden, was zurückgesetzt wird

Zeile 5 und Zeile 6 sind bei der Antwort von Ivan gut erklärt


0

Nur für den Fall, dass jemand nach einem Kotlin-Äquivalent von eindeutigen Zeichen in einer Zeichenfolge mit Bitvektor sucht

fun isUnique(str: String): Boolean {
    var checker = 0
    for (i in str.indices) {
        val bit = str.get(i) - 'a'
        if (checker.and(1 shl bit) > 0) return false
        checker = checker.or(1 shl bit)
    }
    return true
}

Ref: https://www.programiz.com/kotlin-programming/bitwise

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.