อธิบายการใช้บิตเวกเตอร์เพื่อพิจารณาว่าตัวละครทุกตัวมีลักษณะเฉพาะหรือไม่


150

ฉันสับสนเกี่ยวกับการทำงานของเวกเตอร์บิต (ไม่คุ้นเคยกับบิตเวกเตอร์) นี่คือรหัสที่ได้รับ ใครช่วยกรุณาพาฉันไปที่นี้

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ทำคืออะไร?


มันอยู่ใน Java แต่ถ้ามีสิ่งที่คล้ายกันใน C / C ++ ที่จะเป็นประโยชน์สำหรับฉัน
user1136342

101
รหัสนี้มาจากการสัมภาษณ์แคร็กรหัส
เดเจล

2
คุณเคยทดสอบสิ่งนี้หรือไม่? ดูเหมือนว่ามันจะล้มเหลวในการตรวจจับตัวอักษร 'a' ซ้ำเนื่องจากมันถูกตั้งค่าเป็น 0 และเลื่อนไปทางซ้ายมันจะยังคงไว้ที่ 0
Riz

3
โปรดทราบว่าโซลูชันนี้ใช้สำหรับอักขระที่ต่ำกว่า az ซึ่งหมายความว่าเราใช้เพื่อค้นหาข้อมูลที่ซ้ำกันสำหรับอักขระ 26 ตัว ดังนั้นการใช้ 32 บิต int สามารถใช้ที่นี่ หากช่วงมีขนาดใหญ่กว่านั้นการแก้ปัญหาจะไม่ทำงาน
a3.14_Infinity

1
ที่ผู้คนทำผิดพลาดคือพวกเขาสับสนกับไวยากรณ์ตัวดำเนินการกะซ้าย - ซึ่งเป็น 1 ซึ่งถูกย้ายไปทางซ้ายโดย x (= str.charAt (i) - 'a') สถานที่ที่ไม่ใช่บิต x เลื่อนไปทางซ้าย 1 แห่ง
nanosoft

คำตอบ:


100

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อาจจะมีนิด ๆ หน่อย ๆ ได้เร็วขึ้นเพราะการดำเนินการกับบิตมีระดับต่ำมากและสามารถดำเนินการได้ตามที่เป็นโดย CPU BitVector อนุญาตให้เขียนรหัสลับน้อยลงเล็กน้อยและสามารถเก็บค่าสถานะเพิ่มเติมได้

สำหรับการอ้างอิงในอนาคต: บิตเวกเตอร์เป็นที่รู้จักกันว่า bitSet หรือ bitArray นี่คือลิงค์ไปยังโครงสร้างข้อมูลนี้สำหรับภาษา / แพลตฟอร์มต่างๆ:


java มีคลาส BitVector หรือไม่ ฉันไม่พบเอกสารใด ๆ เลย!
Dejell

ขนาดมีขนาดคงที่ซึ่งเป็น 32 บิต นั่นหมายความว่ามันสามารถทดสอบเอกลักษณ์ 32 ตัวเท่านั้นหรือไม่ ฉันได้ทดสอบว่าฟังก์ชันนี้สามารถทดสอบ "abcdefgZZ" เป็นเท็จ แต่ "abcdefg @@" กลับจริง
tli2020

1
Google พาฉันมาที่นี่ @Dejel นี่คือโครงสร้างข้อมูล Java ที่คุณสามารถใช้: docs.oracle.com/javase/7/docs/api/java/util/BitSet.html หวังว่านี่จะช่วยให้ใครบางคนเดินทางผ่าน intertubes
nattyddubbs

@nattyddubbs ขอบคุณฉันได้เพิ่มสิ่งนี้และเชื่อมโยงไปยังคำตอบอื่น ๆ อีกมากมาย
# # # Snowbear

223

ฉันสงสัยว่าคุณได้รับรหัสนี้จากหนังสือเล่มเดียวกันที่ฉันกำลังอ่าน ... รหัสตัวเองที่นี่ไม่ได้เป็นความลับเหมือนกับตัวดำเนินการ - | =, &, และ << ซึ่งไม่ได้ใช้โดยปกติ พวกเราเป็นคนธรรมดา - ผู้เขียนไม่ได้ใส่ใจที่จะใช้เวลาพิเศษในการอธิบายกระบวนการหรือกลไกที่แท้จริงที่เกี่ยวข้องที่นี่ ฉันเป็นเนื้อหาที่มีคำตอบก่อนหน้านี้ในหัวข้อนี้ในการเริ่มต้น แต่เฉพาะในระดับนามธรรม ฉันกลับมาเพราะฉันรู้สึกว่าจำเป็นต้องมีคำอธิบายที่ชัดเจนมากขึ้น - การขาดสิ่งหนึ่งทำให้ฉันรู้สึกไม่สบายใจอยู่เสมอ

โอเปอเรเตอร์ << นี้เป็นตัวเปลี่ยนค่าบิตซ้ายที่ใช้แทนเลขฐานสองของตัวเลขหรือตัวถูกดำเนินการและเลื่อนไปตามสถานที่หลายแห่งที่ระบุโดยตัวถูกดำเนินการหรือหมายเลขทางด้านขวาเช่นในเลขทศนิยมเฉพาะในไบนารี เราคูณด้วยฐาน 2 เมื่อเราเลื่อนขึ้น แต่หลายแห่งไม่ได้ฐาน 10- ดังนั้นเลขทางด้านขวาคือเลขชี้กำลังและเลขทางด้านซ้ายเป็นเลขฐานสองของ 2

ตัวดำเนินการนี้ | = นำตัวถูกดำเนินการไปทางซ้ายและหรือด้วยตัวถูกดำเนินการทางขวา - อันนี้ - '&' และบิตของตัวถูกดำเนินการทั้งสองไปทางซ้ายและขวาของมัน

ดังนั้นสิ่งที่เรามีที่นี่คือตารางแฮชซึ่งถูกเก็บไว้ในเลขฐานสอง 32 บิตทุกครั้งที่ตัวตรวจสอบได้รับหรือต้องการ ( checker |= (1 << val)) ด้วยค่าไบนารีที่กำหนดของตัวอักษรบิตที่สอดคล้องกันมันถูกตั้งค่าเป็นจริง ค่าของตัวละครคือและจะมีตัวตรวจสอบ ( checker & (1 << val)) > 0) - ถ้ามันมากกว่า 0 เรารู้ว่าเรามีสอง - เพราะบิตที่เหมือนกันสองตั้งค่าเป็นจริงและจะกลับมารวมกันจริงหรือ '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' เมื่อเราย้ายทีละขั้นตอน

สตริง 'a'

a      =00000000000000000000000000000001
checker=00000000000000000000000000000000

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

a and checker=0 no dupes condition

สตริง 'az'

checker=00000000000000000000000000000001
z      =00000010000000000000000000000000

z and checker=0 no dupes 

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

สตริง 'azy'

checker= 00000010000000000000000000000001    
y      = 00000001000000000000000000000000 

checker and y=0 no dupes condition 

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

สตริง 'azya'

checker= 00000011000000000000000000000001
a      = 00000000000000000000000000000001

a and checker=1 we have a dupe

ตอนนี้มันประกาศที่ซ้ำกัน


@ ivan-tichy คุณเคยทดสอบสิ่งนี้หรือไม่? ดูเหมือนว่ามันจะล้มเหลวในการตรวจจับตัวอักษร 'a' ซ้ำเนื่องจากมันถูกตั้งค่าเป็น 0 และเลื่อนไปทางซ้ายมันจะยังคงไว้ที่ 0
Riz

1
@Riz ไม่มันเริ่มต้นด้วย '1' เสมออัลกอริทึมจะเลื่อน 1 ตามตัวอักษร ดังนั้นถ้าตัวอักษร 'a' มาหนึ่งครั้งมันจะเป็น 1 ซึ่งก็คือ (.... 000001)
Taylor Halliday

2
@Ivan Man ฉันคิดในสิ่งเดียวกัน แม้แต่คำตอบที่เลือกไม่ได้อธิบายเกี่ยวกับผู้ประกอบการ ขอบคุณสำหรับข้อมูลรายละเอียด
WowBow

ฉันควรสมมติว่าการตรวจสอบที่ไม่ซ้ำกันข้างต้นนั้นใช้ได้เฉพาะกับชุดอักขระที่จัดเรียง (abcd ... z) หรือไม่ ไม่ใช่กับ (bcad ... )
abdul rashid

"ฉันมีความสงสัยที่ด้อมคุณได้รับรหัสนี้จากหนังสือเล่มเดียวกันที่ฉันกำลังอ่าน" เหมือนกันที่นี่ :) ทำให้ฉันหัวเราะ
กระดูกสันหลัง

39

ฉันคิดว่าคำตอบทั้งหมดเหล่านี้อธิบายว่ามันทำงานอย่างไร แต่ฉันรู้สึกเหมือนให้ข้อมูลว่าฉันเห็นอย่างไรดีขึ้นโดยการเปลี่ยนชื่อตัวแปรเพิ่มคนอื่น ๆ และเพิ่มความคิดเห็น:

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
คำอธิบายที่ดี ขอบคุณ!
ฮอร์โมน

คำอธิบายที่ชัดเจน ..
ขอบคุณ

คำอธิบายที่ดี เข้าใจง่าย ขอบคุณ
Anil Kumar

อันนั้นดีที่สุด
Vladimir Nabokov

นี่คือเหตุผลว่าทำไมความคิดเห็นที่ถูกคิดค้น
นาย Suryaa Jha

30

ฉันยังสมมติว่าตัวอย่างของคุณมาจากหนังสือCracking The Code Interviewและคำตอบของฉันเกี่ยวข้องกับบริบทนี้

ในการใช้อัลกอริทึมนี้เพื่อแก้ปัญหาเราต้องยอมรับว่าเราจะส่งตัวอักษรจาก a ถึง z เท่านั้น (ตัวพิมพ์เล็ก)

เนื่องจากมีเพียง 26 ตัวอักษรและสิ่งเหล่านี้จะถูกจัดเรียงอย่างถูกต้องในตารางการเข้ารหัสที่เราใช้สิ่งนี้รับประกันได้ว่าความแตกต่างที่str.charAt(i) - 'a'เป็นไปได้ทั้งหมดจะด้อยกว่าถึง 32 (ขนาดของตัวแปร int checker)

ตามที่อธิบายโดย Snowbear เรากำลังจะใช้checkerตัวแปรเป็นอาร์เรย์ของบิต ให้มีวิธีการตามตัวอย่าง:

สมมติว่า str equals "test"

  • รอบแรก (i = t)

ตัวตรวจสอบ == 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)

ตัวตรวจสอบ == 524288 (00000000000010000000000000000000)

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

และอื่น ๆ .. จนกว่าเราจะพบบิตที่ตั้งค่าไว้แล้วในตัวตรวจสอบสำหรับอักขระเฉพาะผ่านเงื่อนไข

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

หวังว่ามันจะช่วย


2
คำอธิบายที่ดีกว่า IMO ที่เหลือ แต่สิ่งหนึ่งที่ฉันยังไม่ได้รับคือตัวตรวจสอบ = 00000000000010000000000000000000 | 000000000000000000000000000000010000 ไม่ใช่ผู้ประกอบการระดับบิต | = หรือ จะไม่เลือกค่าหนึ่งหรืออื่น ๆ ตั้งแต่? ทำไมมันใช้และตั้งค่าและบิตทั้งสอง?
CodeCrack

@CodeCrack คุณบอกว่ามันเป็นบิตหรือ มันเปรียบเทียบที่ระดับบิตไม่ใช่ระดับ Array หมายเหตุ: int คือ bit Array
MusicMan

7

มีคำตอบที่ยอดเยี่ยมสองสามข้อที่ให้ไว้ข้างต้นแล้ว ดังนั้นฉันไม่ต้องการทำซ้ำสิ่งที่ทุกอย่างพูดแล้ว แต่ต้องการเพิ่มสิ่งต่าง ๆ สองสามอย่างเพื่อช่วยในโปรแกรมข้างต้นเนื่องจากฉันเพิ่งทำงานผ่านโปรแกรมเดียวกันและมีคำถามสองสามข้อ แต่หลังจากใช้เวลาฉันมีความชัดเจนมากขึ้นในโปรแกรมนี้

สิ่งแรกคือ "ตัวตรวจสอบ" ทั้งหมดใช้เพื่อติดตามตัวละครที่ผ่านการใช้งานสตริงไปแล้วเพื่อดูว่ามีตัวละครใดที่กำลังทำซ้ำอยู่หรือไม่

ตอนนี้ "ตัวตรวจสอบ" เป็นชนิดข้อมูลแบบอินดังนั้นมันสามารถมี 32 บิตหรือ 4 ไบต์ (ขึ้นอยู่กับแพลตฟอร์ม) ดังนั้นโปรแกรมนี้สามารถทำงานได้อย่างถูกต้องสำหรับชุดอักขระที่อยู่ในช่วง 32 ตัวอักษร นั่นคือเหตุผลที่โปรแกรมนี้ลบ 'a' จากตัวละครแต่ละตัวเพื่อให้โปรแกรมนี้ทำงานเฉพาะอักขระตัวพิมพ์เล็กเท่านั้น อย่างไรก็ตามหากคุณผสมตัวอักษรพิมพ์เล็กและพิมพ์ใหญ่มันจะไม่ทำงาน

โดยวิธีการถ้าคุณไม่ลบ 'a' จากตัวละครแต่ละตัว (ดูคำสั่งด้านล่าง) จากนั้นโปรแกรมนี้จะทำงานได้อย่างถูกต้องสำหรับ String ที่มีอักขระตัวพิมพ์ใหญ่หรือ String ที่มีอักขระตัวพิมพ์เล็กเท่านั้น ดังนั้นขอบเขตของโปรแกรมด้านบนจึงเพิ่มจากตัวอักษรตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ แต่ไม่สามารถผสมกันได้

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

อย่างไรก็ตามฉันต้องการที่จะเขียนโปรแกรมทั่วไปโดยใช้การทำงานของ Bitwise ซึ่งควรทำงานกับอักขระ ASCII ใด ๆ โดยไม่ต้องกังวลเกี่ยวกับตัวพิมพ์ใหญ่ตัวพิมพ์เล็กตัวเลขหรืออักขระพิเศษใด ๆ ในการดำเนินการนี้ "ตัวตรวจสอบ" ของเราควรใหญ่พอที่จะเก็บอักขระได้ 256 ตัว (ขนาดชุดอักขระ ASCII) แต่ int ใน Java จะไม่ทำงานเพราะสามารถเก็บได้ 32 บิตเท่านั้น ดังนั้นในโปรแกรมด้านล่างฉันใช้คลาส BitSet ที่มีอยู่ใน JDK ซึ่งสามารถมีขนาดที่ผู้ใช้กำหนดส่งผ่านในขณะที่วัตถุบิตเซ็ตอินสแตนซ์

นี่คือโปรแกรมที่ทำสิ่งเดียวกับที่เขียนไว้ข้างต้นโดยใช้ตัวดำเนินการ Bitwise แต่โปรแกรมนี้จะทำงานให้กับ 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;

}

1
ฉันกำลังมองหาวิธีแก้ปัญหานี้ แต่ไม่จำเป็นต้องมีตัวแปร BitSet สองตัว เพียงแค่ติดตามก็เพียงพอแล้ว อัปเดตสำหรับโค้ดลูป: for(int i = 0; i < s.length(); i++) { int charVal = s.charAt(i); if(tracker.get(charVal)) { return false; } tracker.set(charVal); }
zambro

7

การอ่านคำตอบของอีวานด้านบนช่วยฉันได้จริงๆแม้ว่าฉันจะรู้สึกว่ามันแตกต่างกันบ้าง

<<ใน(1 << val)เป็นผู้ประกอบการขยับเล็กน้อย ใช้เวลา1(ซึ่งเป็นเลขฐานสองที่แสดงว่า000000001มีเลขศูนย์ก่อนหน้าเท่าที่คุณต้องการ / ถูกจัดสรรโดยหน่วยความจำ) และเลื่อนไปทางซ้ายด้วยvalช่องว่าง เนื่องจากเราสมมติว่ามีเพียง az และลบaแต่ละครั้งตัวอักษรแต่ละตัวจะมีค่า 0-25 ซึ่งจะเป็นดัชนีของตัวอักษรนั้นจากด้านขวาในการcheckerแสดงบูลีนของจำนวนเต็มเนื่องจากเราจะย้าย1ไปทางซ้ายในchecker valเวลา

ในตอนท้ายของการตรวจสอบแต่ละครั้งเราจะเห็น|=ผู้ดำเนินการ สิ่งนี้จะรวมเลขฐานสองสองตัวแทนที่ทั้งหมด0ด้วย1ถ้ามี1อยู่ในตัวถูกดำเนินการที่ดัชนีนั้น นี่หมายความว่าที่ใดก็ตาม1ที่มีอยู่ใน(1 << val)ที่1จะถูกคัดลอกไปในcheckerขณะที่ทั้งหมดของcheckerของที่มีอยู่ 1 จะได้รับการเก็บรักษาไว้

ในขณะที่คุณสามารถเดาได้1ฟังก์ชั่นที่นี่ในฐานะธงบูลีนเป็นจริง เมื่อเราตรวจสอบเพื่อดูว่าตัวละครมีอยู่แล้วในสตริงเราเปรียบเทียบcheckerซึ่ง ณ จุดนี้เป็นอาร์เรย์ของค่าสถานะบูลีน ( 1ค่า) ที่เป็นดัชนีของตัวละครที่มีการแสดงแล้วกับสิ่งที่เป็นอาร์เรย์ของ ค่าบูลีนที่มี1แฟล็กที่ดัชนีของอักขระปัจจุบัน

&ประกอบการสำเร็จการตรวจสอบนี้ คล้ายกับ|=ที่&ผู้ประกอบการจะคัดลอกกว่า1 เท่านั้นถ้าทั้งสองตัวถูกดำเนินการมี1ที่ดัชนีที่ ดังนั้นโดยพื้นฐานแล้วมีเพียงธงที่มีอยู่ในcheckerสิ่งนั้นและ(1 << val)จะถูกคัดลอกไป ในกรณีนี้หมายความว่าเฉพาะในกรณีที่ตัวละครในปัจจุบันได้รับการแสดงแล้วจะมีได้ทุกที่ในปัจจุบันผลมาจากการ1 checker & (1 << val)และถ้า a 1อยู่ที่ใดก็ได้ในผลลัพธ์ของการดำเนินการนั้นค่าบูลีนที่ส่งคืนคือ> 0และเมธอดส่งคืนค่าเท็จ

นี่คือฉันเดาว่าเหตุใดเวกเตอร์บิตจึงเรียกว่าบิตอาเรย์ เพราะแม้ว่าพวกเขาจะไม่ได้อยู่ในประเภทข้อมูลอาร์เรย์พวกเขาสามารถใช้คล้ายกับวิธีการใช้อาร์เรย์เพื่อเก็บธงบูลีน


1
มีประโยชน์มากขอบคุณสำหรับข้อมูล java ของคุณโรย
Bachiri Taoufiq Abderrahman

4

คำอธิบายง่ายๆ (ด้วยรหัส JS ด้านล่าง)

  • ตัวแปรจำนวนเต็มต่อรหัสเครื่องคืออาร์เรย์แบบ 32 บิต
  • การดำเนินการที่ฉลาดเล็กน้อยคือ 32-bit
  • ไม่เชื่อเรื่องสถาปัตยกรรม OS / CPU หรือระบบตัวเลขที่เลือกของภาษาเช่นDEC64สำหรับ JS
  • วิธีการค้นหาข้อมูลที่ซ้ำกันนี้คล้ายกับการจัดเก็บอักขระในอาร์เรย์ขนาด 32โดยที่เราตั้งค่า0thดัชนีถ้าเราพบaในสตริง1stสำหรับbเป็นต้น
  • อักขระที่ซ้ำกันในสตริงจะมีบิตที่เกี่ยวข้องอยู่หรือในกรณีนี้ให้ตั้งค่าเป็น 1
  • Ivan ยังอธิบายแล้ว : วิธีการคำนวณดัชนีนี้ทำงานในคำตอบก่อนหน้านี้

สรุปการดำเนินงาน:

  • ดำเนินการและดำเนินการระหว่างchecker& indexของตัวละคร
  • ภายในมีทั้ง Int-32-Arrays
  • เป็นการดำเนินการที่ชาญฉลาดระหว่าง 2 เหล่านี้
  • ตรวจสอบifผลลัพธ์ของการดำเนินการได้1
  • ถ้า output == 1
    • checkerตัวแปรมีที่เฉพาะบิตดัชนี THชุดทั้งในอาร์เรย์
    • ดังนั้นมันจึงซ้ำกัน
  • ถ้า output == 0
    • ยังไม่พบตัวละครนี้
    • ทำการดำเนินการORระหว่างchecker& indexของตัวละคร
    • ดังนั้นให้ทำการอัพเดท index-bit เป็น 1
    • กำหนดเอาต์พุตให้กับ checker

สมมติฐาน:

  • เราคิดว่าเราจะได้รับตัวอักษรพิมพ์เล็กทั้งหมด
  • และขนาดนั้นก็เพียงพอแล้ว
  • ดังนั้นเราจึงเริ่มนับดัชนีจาก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

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

ให้แยกรหัสบรรทัดทีละบรรทัด

ตัวตรวจสอบ int = 0; เรากำลังเริ่มต้นตัวตรวจสอบซึ่งจะช่วยให้เราค้นหาค่าที่ซ้ำกัน

int val = str.charAt (i) - 'a'; เราได้รับค่า ASCII ของตัวละครที่ตำแหน่ง 'i'th ของสตริงและลบมันด้วยค่า ASCII ของ' a ' เนื่องจากข้อสันนิษฐานคือสตริงเป็นอักขระที่ต่ำกว่าเท่านั้นจำนวนของอักขระที่ จำกัด ไว้ที่ 26 ถึงแม้ว่าค่าของ 'val' จะเป็น> = 0 เสมอ

ถ้า ((ตัวตรวจสอบ & (1 << val))> 0) คืนค่าเท็จ

ตัวตรวจสอบ | = (1 << val);

ตอนนี้เป็นส่วนที่ยุ่งยาก ให้เราพิจารณาตัวอย่างด้วยสตริง "abcda" นี่ควรกลับเท็จ

สำหรับการวนซ้ำ 1:

ตัวตรวจสอบ: 000000000000000000000000000000000000

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000001

ตัวตรวจสอบ & (1 << val): 00000000000000000000000000000000 ไม่ใช่> 0

ดังนั้นตัวตรวจสอบ: 000000000000000000000000000000000001

สำหรับการวนซ้ำวนซ้ำ 2:

ตัวตรวจสอบ: 000000000000000000000000000000000001

val: 98-97 = 1

1 << 0: 0000000000000000000000000000001010

ตัวตรวจสอบ & (1 << val): 00000000000000000000000000000000 ไม่ใช่> 0

ดังนั้นตัวตรวจสอบ: 000000000000000000000000000000000011

สำหรับการวนซ้ำ 3:

ตัวตรวจสอบ: 000000000000000000000000000000000011

val: 99-97 = 0

1 << 0: 00000000000000000000000000000100

ตัวตรวจสอบ & (1 << val): 00000000000000000000000000000000 ไม่ใช่> 0

ดังนั้นตัวตรวจสอบ: 000000000000000000000000000000000111

สำหรับการวนซ้ำลูป 4:

ตัวตรวจสอบ: 000000000000000000000000000000000111

val: 100-97 = 0

1 << 0: 00000000000000000000000000001000

ตัวตรวจสอบ & (1 << val): 00000000000000000000000000000000 ไม่ใช่> 0

ดังนั้นตัวตรวจสอบ: 0000000000000000000000000000001111

สำหรับการวนซ้ำลูป 5:

ตัวตรวจสอบ: 0000000000000000000000000000001111

val: 97-97 = 0

1 << 0: 0000000000000000000000000000000001

ตัวตรวจสอบ & (1 << val): 00000000000000000000000000000001 คือ> 0

ดังนั้นกลับเท็จ


val: 99-97 = 0 ควรเป็น val: 99-97 = 2 และ val: 100-97 = 0 ควรเป็น 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

โพสต์ก่อนหน้านี้อธิบายได้ดีว่าโค้ดบล็อกทำอะไรและฉันต้องการเพิ่มโซลูชันแบบง่ายของฉันโดยใช้โครงสร้างข้อมูล BitSet java:

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

วิธีที่ฉันเข้าใจโดยใช้ Javascript สมมติว่าอินพุตvar inputChar = "abca"; //find if inputChar has all unique characters

เริ่มกันเลย

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

เหนือเส้นค่าพบไบนารีของตัวละครเป็นครั้งแรกใน inputChar ซึ่งเป็น, A = 97 ใน ASCII แล้วแปลง 97 ไบนารีกลายเป็น 1,100,001

ใน Javascript เช่น: "a".charCodeAt().toString(2) ส่งคืน 1100001

checker = 0 // การเป็นตัวแทนไบนารี 32 บิต = 00000000000000000000000000000

checker = 1100001 | checker; // ตัวตรวจสอบกลายเป็น 1100001 (ในการแทน 32 บิตจะกลายเป็น 000000000 ..... 00001100001)

แต่ฉันต้องการ bitmask ของฉัน ( int checker) ตั้งเพียงหนึ่งบิต แต่ตัวตรวจสอบคือ 1100001

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

ตอนนี้โค้ดด้านบนมีประโยชน์ ฉันเพิ่งลบ 97 เสมอ (ASCII val ของ 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

ให้ใช้valซึ่งถูกตั้งค่าใหม่

คำอธิบายบรรทัดที่ 5 และบรรทัดที่ 6 เป็นคำตอบที่ดี


0

ในกรณีที่ใครก็ตามที่กำลังมองหา kotlin เทียบเท่ากับอักขระที่ไม่ซ้ำกันในสตริงโดยใช้บิตเวกเตอร์

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.