Giải thích việc sử dụng vectơ bit để xác định xem tất cả các ký tự là duy nhất


150

Tôi bối rối về cách một vectơ bit sẽ hoạt động để làm điều này (không quá quen thuộc với vectơ bit). Đây là mã được đưa ra. Ai đó có thể xin vui lòng dẫn tôi qua đây?

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

Đặc biệt, đang checkerlàm gì?


Đó là trong Java nhưng nếu có một cái gì đó tương tự trong C / C ++ sẽ hữu ích hơn cho tôi.
dùng1136342

101
Mã này đã được lấy từ Cracking The Code Interview
Dejell

2
bạn đã thử chưa có vẻ như sẽ không phát hiện được các ký tự 'a' trùng lặp vì nó được đặt thành 0 và dịch chuyển sang trái, nó sẽ vẫn giữ ở mức 0.
Riz

3
Lưu ý rằng giải pháp được sử dụng cho các ký tự az thấp hơn, có nghĩa là chúng tôi đang sử dụng nó để tìm bản sao cho 26 ký tự. Vì vậy, int lấy 32 bit có thể được sử dụng ở đây. Nếu phạm vi đã lớn hơn, thì giải pháp sẽ không hoạt động.
a3,14_Thời gian 17/2/2015

1
Trường hợp mọi người mắc lỗi là họ nhầm lẫn với cú pháp toán tử shift trái - đó là 1 được di chuyển sang trái bởi x (= str.charAt (i) - 'a') vị trí KHÔNG x bit bị dịch chuyển sang trái 1 vị trí.
nanosoft

Câu trả lời:


100

int checkerđược sử dụng ở đây như là một lưu trữ cho bit. Mỗi bit trong giá trị nguyên có thể được coi là một cờ, vì vậy cuối cùng intlà một mảng các bit (cờ). Mỗi bit trong mã của bạn cho biết ký tự có chỉ mục của bit có được tìm thấy trong chuỗi hay không. Bạn có thể sử dụng vector bit cho cùng một lý do thay vì int. Có hai sự khác biệt giữa chúng:

  • Kích thước . intcó kích thước cố định, thường là 4 byte có nghĩa là 8 * 4 = 32 bit (cờ). Vectơ bit thường có thể có kích thước khác nhau hoặc bạn nên chỉ định kích thước trong hàm tạo.

  • API . Với các vectơ bit, bạn sẽ dễ đọc mã hơn, có thể là như thế này:

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

    cho intbạn sẽ có mã logic bit cấp thấp hơn:

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

Cũng có intthể có thể nhanh hơn một chút, bởi vì các hoạt động với các bit ở mức rất thấp và có thể được thực thi theo nguyên trạng của CPU. BitVector cho phép viết mã mật mã ít hơn một chút thay vào đó nó có thể lưu trữ nhiều cờ hơn.

Để tham khảo trong tương lai: vectơ bit còn được gọi là bit set hoặc bitArray. Dưới đây là một số liên kết đến cấu trúc dữ liệu này cho các ngôn ngữ / nền tảng khác nhau:


Java có lớp BitVector không? Tôi không thể tìm thấy bất kỳ tài liệu cho nó!
Dejell

Kích thước có kích thước cố định, là 32 bit. Điều đó có nghĩa là nó chỉ có thể kiểm tra tính độc đáo của 32 ký tự? Tôi đã kiểm tra rằng, chức năng này có thể kiểm tra "abcdefgZZ" là sai, nhưng "abcdefg @@" trả về đúng.
tli2020

1
Google dẫn tôi đến đây. @Dejel Đây là cấu trúc dữ liệu java mà bạn có thể sử dụng: docs.oracle.com/javase/7/docs/api/java/util/Bitset.html . Hy vọng rằng điều này sẽ giúp ai đó đi qua intertubes.
nattyddubbs

@nattyddubbs, cảm ơn, tôi đã thêm điều này và một số liên kết khác vào câu trả lời
Snowbear

222

Tôi có một nghi ngờ lén lút bạn đã nhận được mã này từ cùng một cuốn sách tôi đang đọc ... Bản thân mã ở đây không khó hiểu như các toán tử- | =, &, và << thường không được sử dụng bởi chúng tôi cư sĩ - tác giả đã không bận tâm đến việc dành thêm thời gian để giải thích quá trình cũng như các cơ chế thực tế liên quan ở đây là gì. Tôi đã hài lòng với câu trả lời trước đó về chủ đề này ngay từ đầu nhưng chỉ ở mức độ trừu tượng. Tôi quay lại với nó bởi vì tôi cảm thấy cần phải có một lời giải thích cụ thể hơn - việc thiếu một người luôn khiến tôi cảm thấy khó chịu.

Toán tử << này là một shifter bit trái, nó lấy biểu diễn nhị phân của số đó hoặc toán hạng và dịch chuyển nó qua nhiều vị trí được chỉ định bởi toán hạng hoặc số ở bên phải như số thập phân chỉ trong các nhị phân. Chúng tôi đang nhân với cơ sở 2 - khi chúng tôi di chuyển lên tuy nhiên nhiều nơi không phải là căn cứ 10 - vì vậy số bên phải là số mũ và số bên trái là bội số cơ bản của 2.

Toán tử này | = lấy toán hạng bên trái và hoặc nó với toán hạng ở bên phải - và cái này - '&' và là các bit của cả hai toán hạng sang trái và phải của nó.

Vì vậy, những gì chúng ta có ở đây là một bảng băm đang được lưu trữ trong số nhị phân 32 bit mỗi khi trình kiểm tra lấy or'd ( checker |= (1 << val)) với giá trị nhị phân được chỉ định của một chữ cái mà bit tương ứng của nó được đặt thành đúng. Giá trị của ký tự là và với trình kiểm tra ( checker & (1 << val)) > 0) - nếu lớn hơn 0, chúng tôi biết rằng chúng tôi có một bản sao - vì hai bit giống nhau được đặt thành true và sẽ cùng nhau trả về giá trị true hoặc '1' '.

Có 26 vị trí nhị phân, mỗi vị trí tương ứng với một chữ cái viết thường - tác giả đã nói rằng giả sử chuỗi chỉ chứa các chữ cái viết thường - và điều này là do chúng ta chỉ còn lại 6 vị trí (trong số nguyên 32 bit) và hơn chúng ta bị va chạm

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

Vì vậy, đối với một chuỗi đầu vào 'azya', khi chúng ta di chuyển từng bước

chuỗi 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

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

a and checker=0 no dupes condition

chuỗi 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

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

chuỗi 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

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

chuỗi 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

Bây giờ, nó tuyên bố một bản sao


@ ivan-tichy bạn đã thử chưa? có vẻ như sẽ không phát hiện được các ký tự 'a' trùng lặp vì nó được đặt thành 0 và dịch chuyển sang trái, nó sẽ vẫn giữ ở mức 0.
Riz

1
@Riz Không, nó luôn bắt đầu bằng '1', thuật toán thay đổi 1 dựa trên chữ cái. Vì vậy, nếu chữ 'a' xuất hiện một lần, nó sẽ là 1, đó là (.... 000001).
Taylor Halliday

2
@Ivan Man, tôi đã suy nghĩ điều tương tự. Ngay cả câu trả lời được chọn cũng không giải thích về các toán tử. Cảm ơn bạn đã thông tin chi tiết.
WowBow

Tôi có nên giả sử các công việc kiểm tra duy nhất ở trên chỉ với bộ ký tự được sắp xếp (abcd ... z) không? không phải với (bcad ...)
abdul rashid

"Tôi có một nghi ngờ lén lút bạn đã nhận được mã này từ cùng một cuốn sách tôi đang đọc" ở đây :) làm tôi cười
xương sống

39

Tôi nghĩ rằng tất cả những câu trả lời này giải thích cách thức hoạt động của nó, tuy nhiên tôi cảm thấy muốn đưa ra ý kiến ​​của mình về cách tôi thấy nó tốt hơn, bằng cách đổi tên một số biến, thêm một số biến khác và thêm nhận xét vào nó:

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
Giải thích tuyệt vời. Cảm ơn bạn!
Hormigas

Giải thích rõ
ràng..Cảm

Giải thích tuyệt vời. Dễ hiểu. Cảm ơn bạn
Anil Kumar

Đó là điều tốt nhất
Vladimir Nabokov

Đây là lý do tại sao ý kiến ​​được phát minh.
Ông Suryaa Jha

30

Tôi cũng cho rằng ví dụ của bạn xuất phát từ cuốn sách Cracking The Code Interview và câu trả lời của tôi có liên quan đến bối cảnh này.

Để sử dụng thuật toán này để giải quyết vấn đề, chúng tôi phải thừa nhận rằng chúng tôi sẽ chỉ chuyển các ký tự từ a đến z (chữ thường).

Vì chỉ có 26 chữ cái và chúng được sắp xếp hợp lý trong bảng mã hóa mà chúng tôi sử dụng, điều này đảm bảo cho chúng tôi rằng tất cả các khác biệt tiềm năng str.charAt(i) - 'a'sẽ thấp hơn 32 (kích thước của biến int checker).

Theo giải thích của Snowbear, chúng tôi sắp sử dụng checkerbiến như một mảng bit. Hãy có một cách tiếp cận bằng ví dụ:

Hãy cùng nói nào str equals "test"

  • Vượt qua đầu tiên (i = t)

người kiểm tra == 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)
  • Vượt qua thứ hai (i = e)

người kiểm tra == 524288 (00000000000010000000000000000000)

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

và cứ thế .. cho đến khi chúng ta tìm thấy một bit đã được thiết lập trong trình kiểm tra cho một ký tự cụ thể thông qua điều kiện

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

Hy vọng nó giúp


2
Giải thích tốt hơn nhiều so với IMO còn lại nhưng một điều tôi vẫn không nhận được là kiểm tra = 00000000000010000000000000000000 | 00000000000000000000000000010000 không phải là toán tử bitwise | = OR. Sẽ không chọn một giá trị này hay giá trị khác kể từ khi? Tại sao nó sử dụng và thiết lập và cả hai bit?
CodeCrack

@CodeCrack bạn nói nó là bit OR. Nó so sánh ở cấp độ bit không phải ở cấp độ mảng bit. Lưu ý: int là bit Array
MusicMan

7

Có một vài câu trả lời xuất sắc đã được cung cấp ở trên. Vì vậy, tôi không muốn lặp lại những gì đã nói. Nhưng tôi muốn thêm vài điều để giúp với chương trình trên vì tôi chỉ làm việc qua cùng một chương trình và có vài câu hỏi nhưng sau khi dành một chút thời gian, tôi đã hiểu rõ hơn về chương trình này.

Trước hết, "trình kiểm tra" được sử dụng để theo dõi ký tự đã đi qua Chuỗi để xem liệu có bất kỳ ký tự nào đang được lặp lại hay không.

Bây giờ "trình kiểm tra" là một kiểu dữ liệu int nên nó chỉ có thể có 32 bit hoặc 4 byte (tùy thuộc vào nền tảng) để chương trình này chỉ có thể hoạt động chính xác cho một bộ ký tự trong phạm vi 32 ký tự. Đó là lý do, chương trình này trừ 'a' từ mỗi ký tự để làm cho chương trình này chỉ chạy cho các ký tự chữ thường. Tuy nhiên nếu bạn trộn các ký tự chữ thường và chữ hoa thì nó sẽ không hoạt động.

Nhân tiện, nếu bạn không trừ 'a' từ mỗi ký tự (xem câu lệnh bên dưới) thì chương trình này sẽ hoạt động chính xác chỉ với Chuỗi có ký tự chữ hoa hoặc Chuỗi chỉ có ký tự chữ thường. Vì vậy, phạm vi của chương trình trên cũng tăng từ các ký tự chữ thường sang ký tự chữ hoa nhưng chúng không thể trộn lẫn với nhau.

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

Tuy nhiên, tôi muốn viết một chương trình chung sử dụng Hoạt động Bitwise, hoạt động cho mọi ký tự ASCII mà không phải lo lắng về chữ hoa, chữ thường, số hoặc bất kỳ ký tự đặc biệt nào. Để thực hiện việc này, "trình kiểm tra" của chúng tôi phải đủ lớn để lưu trữ 256 ký tự (kích thước Bộ ký tự ASCII). Nhưng một int trong Java sẽ không hoạt động vì nó chỉ có thể lưu trữ 32 bit. Do đó trong chương trình dưới đây, tôi đang sử dụng lớp BitSet có sẵn trong JDK, có thể có bất kỳ kích thước do người dùng xác định nào được thông qua trong khi khởi tạo một đối tượng BitSet.

Đây là một chương trình thực hiện tương tự như chương trình trên được viết bằng toán tử Bitwise nhưng chương trình này sẽ hoạt động cho Chuỗi với bất kỳ ký tự nào từ bộ ký tự 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;

}

1
Tôi đã tìm kiếm giải pháp này, tuy nhiên không cần hai biến Bitset. Chỉ cần theo dõi là đủ. Đã cập nhật mã vòng lặp: for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
zambro

7

Đọc câu trả lời của Ivan ở trên thực sự giúp tôi, mặc dù tôi sẽ diễn đạt nó hơi khác.

Các <<trong (1 << val)là một toán tử dịch chuyển một chút. Nó nhận 1(mà trong nhị phân được biểu diễn dưới dạng 000000001, có nhiều số 0 trước bạn muốn / được phân bổ theo bộ nhớ) và dịch chuyển nó sang bên trái bởi valkhoảng trắng. Vì chúng tôi chỉ giả sử az và trừ amỗi lần, mỗi chữ cái sẽ có giá trị 0-25, đó sẽ là chỉ số của chữ cái đó từ bên phải trong checkerbiểu diễn boolean của nhà tích hợp, vì chúng tôi sẽ di chuyển 1sang bên trái theo checker valthời gian.

Vào cuối mỗi kiểm tra, chúng tôi thấy các |=nhà điều hành. Điều này hợp nhất hai số nhị phân, thay thế tất cả 0bằng số 1nếu 1tồn tại trong toán hạng tại chỉ mục đó. Ở đây, điều đó có nghĩa là bất cứ nơi nào 1tồn tại (1 << val), nó 1sẽ được sao chép vào checker, trong khi tất cả số checker1 hiện có sẽ được bảo tồn.

Như bạn có thể đoán, một 1hàm ở đây là cờ boolean cho đúng. Khi chúng tôi kiểm tra xem một ký tự đã được biểu diễn trong chuỗi chưa, chúng tôi so sánh checker, tại thời điểm này về cơ bản là một mảng các cờ boolean ( 1giá trị) tại các chỉ mục của các ký tự đã được biểu diễn, với thực chất là một mảng của giá trị boolean với một 1cờ ở chỉ mục của ký tự hiện tại.

Người &vận hành hoàn thành kiểm tra này. Tương tự như |=, &toán tử sẽ sao chép 1 chỉ khi cả hai toán hạng có 1tại chỉ mục đó. Vì vậy, về cơ bản, chỉ các cờ đã có trong checkerđó cũng được thể hiện trong (1 << val)sẽ được sao chép qua. Trong trường hợp này, điều đó có nghĩa là chỉ khi nhân vật hiện tại đã được đại diện thì sẽ có một 1món quà ở bất cứ đâu trong kết quả của checker & (1 << val). Và nếu a 1có mặt ở bất cứ đâu trong kết quả của hoạt động đó, thì giá trị của boolean được trả về là > 0và phương thức trả về false.

Đây là, tôi đoán, tại sao vectơ bit còn được gọi là mảng bit . Bởi vì, mặc dù chúng không thuộc kiểu dữ liệu mảng, chúng có thể được sử dụng tương tự như cách các mảng được sử dụng để lưu trữ các cờ boolean.


1
Rất hữu ích, Cảm ơn bạn đã rắc thông tin java của bạn.
Bachiri Taoufiq Abderrahman

4

Giải thích đơn giản (với mã JS bên dưới)

  • Một biến số nguyên cho mỗi mã máy là một mảng 32 bit
  • Tất cả các hoạt động khôn ngoan bit là 32-bit
  • Họ không biết gì về kiến ​​trúc HĐH / CPU hoặc hệ thống số được chọn của ngôn ngữ, ví dụ như DEC64đối với JS.
  • Phương pháp phát hiện sự trùng lặp này cũng tương tự như lưu trữ các nhân vật trong một loạt các kích thước 32 nơi, chúng tôi đặt 0thchỉ số nếu chúng ta tìm thấy atrong chuỗi, 1stcho b& vân vân.
  • Một ký tự trùng lặp trong chuỗi sẽ có bit tương ứng của nó, hoặc, trong trường hợp này, được đặt thành 1.
  • Ivan đã giải thích : Cách tính chỉ số này hoạt động trong câu trả lời trước này .

Tóm tắt hoạt động:

  • Thực hiện thao tác giữa checker& indexcủa nhân vật
  • Bên trong cả hai đều Int-32-Arrays
  • Đó là một hoạt động khôn ngoan giữa 2.
  • Kiểm tra ifđầu ra của hoạt động được1
  • nếu output == 1
    • Các checkerbiến có mà chút chỉ số thứ đặc biệt bộ trong cả hai mảng
    • Vì vậy, nó là một bản sao.
  • nếu output == 0
    • Nhân vật này đã không được tìm thấy cho đến nay
    • Thực hiện thao tác OR giữa checker& indexcủa ký tự
    • Qua đó, cập nhật bit chỉ mục thành 1
    • Chỉ định đầu ra cho checker

Giả định:

  • Chúng tôi đã giả định rằng chúng tôi sẽ nhận được tất cả các ký tự chữ thường
  • Và, kích thước 32 là đủ
  • Do đó, chúng tôi đã bắt đầu chỉ số của chúng tôi đếm từ 96 làm điểm tham chiếu khi xem xét mã ascii cho a97

Đưa ra dưới đây là mã nguồn 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

Lưu ý rằng trong JS, mặc dù số nguyên là 64 bit, thao tác khôn ngoan một chút luôn được thực hiện trên 32 bit.

Ví dụ: Nếu chuỗi là aa:

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

Hãy chia nhỏ dòng mã theo từng dòng.

kiểm tra int = 0; Chúng tôi đang bắt đầu một trình kiểm tra sẽ giúp chúng tôi tìm thấy các giá trị trùng lặp.

int val = str.charAt (i) - 'a'; Chúng tôi đang nhận giá trị ASCII của ký tự ở vị trí 'thứ i của chuỗi và trừ nó với giá trị ASCII là' a '. Vì giả định là chuỗi chỉ có các ký tự thấp hơn, số lượng ký tự được giới hạn ở 26. Hece, giá trị của 'val' sẽ luôn là> = 0.

if ((kiểm tra & (1 << val))> 0) trả về false;

kiểm tra | = (1 << val);

Bây giờ đây là phần khó khăn. Cho phép chúng tôi xem xét một ví dụ với chuỗi "abcda". Điều này lý tưởng nên trả lại sai.

Đối với vòng lặp 1:

Người kiểm tra: 0000000000000000000000000000000000

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000101

người kiểm tra & (1 << val): 0000000000000000000000000000000000 không> 0

Do đó kiểm tra: 00000000000000000000000000000001

Đối với vòng lặp 2:

Người kiểm tra: 00000000000000000000000000000001

val: 98-97 = 1

1 << 0: 00000000000000000000000000000010

người kiểm tra & (1 << val): 0000000000000000000000000000000000 không> 0

Do đó kiểm tra: 00000000000000000000000000000011

Đối với vòng lặp 3:

Người kiểm tra: 00000000000000000000000000000011

val: 99-97 = 0

1 << 0: 00000000000000000000000000000100

người kiểm tra & (1 << val): 0000000000000000000000000000000000 không> 0

Do đó kiểm tra: 00000000000000000000000000000111

Đối với vòng lặp 4:

Người kiểm tra: 00000000000000000000000000000111

val: 100-97 = 0

1 << 0: 00000000000000000000000000001000

người kiểm tra & (1 << val): 0000000000000000000000000000000000 không> 0

Do đó kiểm tra: 00000000000000000000000000001111

Đối với vòng lặp 5:

Người kiểm tra: 00000000000000000000000000001111

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000101

người kiểm tra & (1 << val): 0000000000000000000000000000000101 là> 0

Do đó trả về sai.


val: 99-97 = 0 nên là val: 99-97 = 2 và val: 100-97 = 0 nên là 3
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

Bài viết trước giải thích rõ khối mã làm gì và tôi muốn thêm Giải pháp đơn giản của mình bằng cấu trúc Dữ liệu java BitSet:

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

Cách tôi hiểu bằng cách sử dụng Javascript. Giả sử đầu vàovar inputChar = "abca"; //find if inputChar has all unique characters

Hãy bắt đầu

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

Dòng trên Tìm giá trị nhị phân của ký tự đầu tiên trong inputChar là a , a = 97 trong ascii, sau đó chuyển đổi 97 thành nhị phân thành 1100001 .

Trong Javascript Ví dụ: "a".charCodeAt().toString(2) trả về 1100001

checker = 0 // biểu diễn nhị phân 32 bit = 0000000000000000000000000

checker = 1100001 | checker; // trình kiểm tra trở thành 1100001 (Trong biểu diễn 32 bit, nó trở thành 000000000 ..... 00001100001)

Nhưng tôi muốn bitmask ( int checker) của tôi chỉ đặt một bit, nhưng trình kiểm tra là 1100001

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

Bây giờ mã ở trên có ích. Tôi chỉ trừ 97 luôn (giá trị ASCII của 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

Cho phép sử dụng valđược đặt lại

Dòng 5 và dòng 6 được giải thích rõ @Ivan trả lời


0

Chỉ trong trường hợp nếu bất cứ ai đang tìm kiếm kotlin tương đương với các ký tự duy nhất trong chuỗi bằng vectơ bit

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
}

Tham chiếu: https://www.programiz.com/kotlin-programming/bitwise

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.