Объясните использование битового вектора для определения того, являются ли все символы уникальными

Я смущен тем, как будет работать бит-вектор (не слишком хорошо знакомы с битовыми векторами). Вот приведенный код. Может ли кто-нибудь пропустить меня через это?

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

В частности, что делает checker?

Ответ 1

int checker используется здесь как хранилище для бит. Каждый бит в целочисленном значении можно рассматривать как флаг, поэтому в конечном итоге int представляет собой массив бит (флаг). Каждый бит в вашем коде указывает, был ли символ с индексом бит найден в строке или нет. Вы можете использовать бит-вектор по той же причине вместо int. Между ними есть два отличия:

  • Размер. int имеет фиксированный размер, обычно 4 байта, что означает 8 * 4 = 32 бита (флаги). Битовый вектор обычно может иметь разный размер, или вы должны указать размер в конструкторе.

  • API. С битовыми векторами вам будет легче читать код, возможно, что-то вроде этого:

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

    для int у вас будет младший бит логического кода:

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

Также возможно, что int может быть немного быстрее, потому что операции с битами очень низкие и могут выполняться как-то по процессору. BitVector позволяет писать немного менее критический код, а также хранить больше флагов.

Для справок в будущем: бит-бит также известен как bitSet или bitArray. Вот некоторые ссылки на эту структуру данных для разных языков/платформ:

Ответ 2

У меня есть подозрительное подозрение, что вы получили этот код из той же книги, которую я читаю... Сам код здесь не так загадочен, как операторы- | =, &, и < которые обычно не используются нами мирянами - автор не стал брать лишний тайм-аут в объяснение процесса и то, что здесь подразумевает настоящая механика. Я был доволен предыдущим ответом на этот поток в начале, но только на абстрактном уровне. Я вернулся к нему, потому что я чувствовал, что нужно быть более конкретным объяснением - отсутствие одного всегда оставляет меня с беспокойством.

Этот оператор < является левым побитовым сдвигом, он принимает двоичное представление этого числа или операнда и сдвигает его во все количество мест, заданных операндом или номером справа, как в десятичных числах только в двоичных файлах. Мы умножаемся на базу 2 - когда мы перемещаемся вверх, однако многие места не основаны на 10, поэтому число справа является показателем, а число слева является базой, кратной 2.

Этот оператор | = принимает операнд слева и/или с операндом справа, а этот - "&" и битами обоих операндов слева и справа от него.

Итак, мы имеем здесь хеш-таблицу, которая хранится в 32-битном двоичном числе каждый раз, когда контролер получает or'd (checker |= (1 << val)) с указанным двоичным значением буквы, соответствующей его биту, который он задает к истине. Символьное значение имеет значение с контрольной суммой (checker & (1 << val)) > 0) - если оно больше 0, мы знаем, что у нас есть обман, потому что два одинаковых бита, равные true и 'вместе, возвратят true или' 1 '.

Есть 26 двоичных мест, каждая из которых соответствует строчной букве - автор сказал, что предположить, что строка содержит только строчные буквы, и это потому, что у нас есть только 6 (в 32-битных целых) местах, и мы получаем столкновение

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

Итак, для входной строки 'azya', когда мы шаг за шагом двигаемся

string 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

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

a and checker=0 no dupes condition

string 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

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

string 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

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

string 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Теперь он объявляет дубликат

Ответ 3

Я также предполагаю, что ваш пример исходит из книги Cracking the Code Interview, и мой ответ связан с этим контекстом.

Чтобы использовать этот алгоритм для решения проблемы, мы должны признать, что мы будем передавать символы только от a до z (строчные буквы).

Поскольку имеется только 26 букв, и они правильно сортируются в используемой таблице кодирования, это гарантирует нам, что все потенциальные различия str.charAt(i) - 'a' будут уступать 32 (размер переменной int checker).

Как объясняется Snowbear, мы собираемся использовать переменную checker как массив бит. Давайте рассмотрим пример:

Скажем str equals "test"

  • Первый проход (i = t)

checker == 0 (000000000000000000000000000000000000)

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)
  • Второй проход (i = e)

checker == 524288 (00000000000010000000000000000000)

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

и т.д., пока мы не найдем уже установленный бит в checker для определенного символа с помощью условия

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

Надеюсь, что это поможет

Ответ 4

Я думаю, что все эти ответы объясняют, как это работает, однако я чувствовал, что даю свой вклад в то, как я увидел это лучше, переименовав некоторые переменные, добавив некоторые другие и добавив к нему комментарии:

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

Ответ 5

Есть несколько отличных ответов, уже представленных выше. Поэтому я не хочу повторять то, что все уже сказано. Но я хотел добавить несколько вещей, чтобы помочь с вышеупомянутой программой, поскольку я просто работал через одну и ту же программу, и у меня было несколько вопросов, но, проведя некоторое время, у меня есть больше ясности в этой программе.

Прежде всего "checker" используется для отслеживания символа, который уже прошел в String, чтобы увидеть, повторяются ли какие-либо символы.

Теперь "checker" - это тип данных int, поэтому он может иметь только 32 бита или 4 байта (в зависимости от платформы), поэтому эта программа может работать корректно только для набора символов в диапазоне от 32 символов. По этой причине эта программа вычитает "a" из каждого символа, чтобы эта программа выполнялась только для символов нижнего регистра. Однако, если вы смешиваете символы нижнего и верхнего регистра, это не сработает.

Кстати, если вы не вычтите "a" из каждого символа (см. инструкцию ниже), то эта программа будет корректно работать только для String с символами верхнего регистра или String с только строчными символами. Таким образом, объем вышеперечисленной программы увеличивается также от символов нижнего регистра до символов верхнего регистра, но их нельзя смешивать.

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

Однако я хотел написать общую программу с использованием Побитовой операции, которая должна работать для любых символов ASCII, не беспокоясь о верхнем регистре, нижнем регистре, числах или любом специальном символе. Для этого наш "контролер" должен быть достаточно большим, чтобы хранить 256 символов (размер набора символов ASCII). Но int в Java не будет работать, поскольку он может хранить только 32 бита. Следовательно, в нижеприведенной программе я использую класс BitSet, доступный в JDK, который может иметь любой определенный пользователем размер, переданный при создании экземпляра объекта BitSet.

Вот программа, которая делает то же самое, что и предыдущая программа, написанная с помощью Побитового оператора, но эта программа будет работать для String с любым символом из набора символов ASCII.

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;

}

Ответ 6

Чтение ответа Ивана выше действительно помогло мне, хотя я бы назвал его несколько иначе.

<< in (1 << val) - оператор смещения бит. Он принимает 1 (который в двоичном выражении представлен как 000000001, с таким количеством предшествующих нулей, сколько вам нравится/выделено памятью) и сдвигает его влево на пробелы val. Поскольку мы принимаем только az и вычитаем a каждый раз, каждая буква будет иметь значение 0-25, которое будет индексом букв справа в логическом представлении checker integer, так как мы будем перемещать 1 влево в checker val раза.

В конце каждой проверки мы видим оператор |=. Это объединяет два двоичных числа, заменяя все 0 на 1, если a 1 существует в обоих операндах этого индекса. Здесь это означает, что везде, где a 1 существует в (1 << val), 1 будет скопирован в checker, тогда как все checker существующие 1 будут сохранены.

Как вы можете догадаться, 1 функционирует здесь как логический флаг для true. Когда мы проверяем, представлен ли символ в строке, мы сравниваем checker, который на данный момент представляет собой массив булевых флагов (1 values) в индексах символов, которые уже были представлены, с что по существу является массивом булевых значений с флагом 1 в индексе текущего символа.

Оператор & выполняет эту проверку. Подобно |=, оператор & будет копировать только <<22 > , если оба операнда имеют 1 в этом индексе. Поэтому, по существу, будут скопированы только флаги, уже присутствующие в checker, которые также представлены в (1 << val). В этом случае это означает, что только если текущий символ уже был представлен, будет присутствовать 1 в любом месте в результате checker & (1 << val). И если a 1 присутствует где-нибудь в результате этой операции, тогда значение возвращаемого логического значения равно > 0, и метод возвращает false.

Это, я думаю, почему битовые векторы также называются битными массивами. Поскольку, даже если они не относятся к типу данных массива, их можно использовать аналогично тому, как используются массивы для хранения булевых флагов.

Ответ 7

Позволяет разбить код по строкам.

int checker = 0; Мы начинаем проверку, которая поможет нам найти повторяющиеся значения.

int val = str.charAt(i) - 'a'; Мы получаем значение ASCII символа в "i-й позиции строки и вычитаем его с помощью значения ASCII of 'a'. Поскольку предположение состоит в том, что строка имеет только нижние символы, количество символов ограничено 26. Hece, значение" val" всегда будет >= 0.

if ((checker и (1 < val)) > 0) возвращает false;

checker | = (1 < val);

Теперь это сложная часть. Давайте рассмотрим пример со строкой "abcda". Это должно идеально вернуть значение false.

Итерация цикла 1:

Проверка: 0000000000000000000000000000000000

val: 97-97 = 0

1 < 0: 0000000000000000000000000000000000

checker и (1 < val): 000000000000000000000000000000000000 не > 0

Следовательно, checker: 000000000000000000000000000000000001

Итерация цикла 2:

Checker: 00000000000000000000000000000001

val: 98-97 = 1

1 < 0: 000000000000000000000000000000000010

checker и (1 < val): 000000000000000000000000000000000000 не > 0

Следовательно, checker: 000000000000000000000000000000000011

Итерация цикла 3:

Checker: 0000000000000000000000000000000000

val: 99-97 = 0

1 < 0: 0000000000000000000000000000000000

checker и (1 < val): 000000000000000000000000000000000000 не > 0

Следовательно, checker: 000000000000000000000000000000000111

Итерация цикла 4:

Checker: 0000000000000000000000000000000111

val: 100-97 = 0

1 < 0: 000000000000000000000000000000000

checker и (1 < val): 000000000000000000000000000000000000 не > 0

Следовательно, checker: 0000000000000000000000000000001111

Итерация цикла 5:

Проверка: 00000000000000000000000000001111

val: 97-97 = 0

1 < 0: 0000000000000000000000000000000000

checker и (1 < val): 000000000000000000000000000000000001 > 0

Следовательно, верните false.

Ответ 8

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:;<=>[email protected][\]^_`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;
}

Ответ 9

Простое объяснение (с JS-кодом ниже)

  • Целочисленная переменная для машинного кода представляет собой 32-разрядный массив
  • Все битовые операции 32-bit
  • Они не зависят от архитектуры ОС/ЦП или выбранной системы номеров языка, например. DEC64 для JS.
  • Этот метод поиска дублирования аналогичен хранению символов в массиве размером 32, где мы устанавливаем индекс 0th, если находим a в строке, 1st для b и т.д.
  • У повторяющегося символа в строке будет свой бит, занятый бит, или в этом случае будет установлено значение 1.
  • Иван уже объяснил: как этот индекс вычисляется в этом предыдущем ответе.

Сводка операций:

  • Выполните операцию И между checker и index символа
  • Внутренне оба являются Int-32-Arrays
  • Это двоякая операция между этими двумя.
  • Проверьте if вывод операции 1
  • если output == 1
    • Переменная checker имеет тот конкретный индексный бит, установленный в обоих массивах
    • Таким образом, это дубликат.
  • если output == 0
    • Этот символ еще не найден.
    • Выполните операцию ИЛИ между checker и index символа
    • Таким образом, обновление индекса-th до 1
    • Назначьте вывод checker

Предположения:

  • Мы предположили, что мы получим все символы нижнего регистра
  • И, этого размера 32 достаточно
  • Таким образом, мы начали наш индекс, считая от 96 в качестве ссылки точки с учетом ASCii код a является 97

Ниже приведен исходный код JavaScript.

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

Примечание, что в JS, несмотря на то, что целые числа состоят из 64 бит, битовая операция всегда выполняется на 32 бита.

Пример: Если строка aa, то:

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

я = 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

я = 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