ค้นหา“ hole” ในรายการตัวเลข


14

วิธีที่เร็วที่สุดในการค้นหาจำนวนเต็ม (เล็กที่สุด) แรกที่ไม่มีอยู่ในรายการของจำนวนเต็มไม่ได้เรียง (และนั่นคือมากกว่าค่าที่เล็กที่สุดของรายการ)?

วิธีดั้งเดิมของฉันคือการเรียงลำดับพวกเขาและก้าวผ่านรายการมีวิธีที่ดีกว่า


6
@Jodrell ฉันคิดว่าการเรียงลำดับความก้าวหน้าที่ไม่มีที่สิ้นสุดนั้นคงเป็นเรื่องยาก ;-)
maple_shaft

3
@maple_shaft ตกลงอาจใช้เวลาสักครู่
Jodrell

4
คุณจะกำหนดเป็นอันดับแรกสำหรับรายการที่ไม่เรียงลำดับอย่างไร
Jodrell

1
ฉันเพิ่งรู้ว่านี่อาจเป็นของ StackOverflow เนื่องจากไม่ใช่ปัญหาเชิงแนวคิดจริงๆ
JasonTrue

2
@JasonTrue จากคำถามที่พบบ่อยIf you have a question about… •algorithm and data structure conceptsมันอยู่ในหัวข้อ IMHO
maple_shaft

คำตอบ:


29

สมมติว่าคุณหมายถึง "จำนวนเต็ม" เมื่อคุณพูดว่า "number" คุณสามารถใช้ bitvector ขนาด 2 ^ n โดยที่ n คือจำนวนองค์ประกอบ (พูดว่าช่วงของคุณมีจำนวนเต็มระหว่าง 1 ถึง 256 จากนั้นคุณสามารถใช้ 256- บิตหรือ 32 ไบต์บิตบิตเตอร์) เมื่อคุณเจอจำนวนเต็มในตำแหน่ง n ของช่วงให้ตั้งบิตที่ n

เมื่อคุณระบุคอลเลกชันของจำนวนเต็มเสร็จแล้วคุณวนซ้ำบิตในบิตเวอเตอร์ของคุณโดยมองหาตำแหน่งของบิตใด ๆ ที่ตั้งค่า 0 ตอนนี้พวกเขาตรงกับตำแหน่งที่ n ของจำนวนเต็มที่หายไปของคุณ

นี่คือ O (2 * N) ดังนั้น O (N) และอาจมีประสิทธิภาพมากกว่าหน่วยความจำเรียงลำดับรายการทั้งหมด


6
เป็นการเปรียบเทียบโดยตรงถ้าคุณมีจำนวนเต็ม 32 บิตที่เป็นบวก แต่ 1 คุณสามารถแก้ปัญหาจำนวนเต็มที่หายไปในหน่วยความจำครึ่งกิกะไบต์ หากคุณเรียงลำดับคุณจะต้องใช้หน่วยความจำมากกว่า 8 กิกะไบต์ และการเรียงลำดับยกเว้นในกรณีพิเศษเช่นนี้ (รายการของคุณจะถูกจัดเรียงเมื่อคุณมี bitvector) เกือบจะเป็น log n หรือแย่กว่าเสมอดังนั้นยกเว้นในกรณีที่ค่าคงที่เมื่อเทียบกับความซับซ้อนในค่าใช้จ่าย
JasonTrue

1
เกิดอะไรขึ้นถ้าคุณไม่รู้จักช่วงก่อน
Blrfl

2
หากคุณมีชนิดข้อมูลจำนวนเต็ม Blrfl คุณจะรู้ขอบเขตสูงสุดของช่วงแม้ว่าคุณจะไม่มีข้อมูลเพียงพอที่จะ จำกัด ให้แคบลงอีก หากคุณรู้ว่ามันเป็นรายการเล็ก ๆ แต่ไม่รู้ขนาดที่แน่นอนการเรียงลำดับอาจเป็นวิธีที่ง่ายกว่า
JasonTrue

1
หรือทำลูปอื่นก่อนผ่านรายการเพื่อค้นหาองค์ประกอบที่เล็กที่สุดและใหญ่ที่สุด จากนั้นคุณสามารถจัดสรรอาร์เรย์ขนาดที่แน่นอนด้วยค่าที่น้อยที่สุดเป็นออฟเซ็ตพื้นฐาน ยังคงดำเนินต่อไป).
รักษาความปลอดภัย

1
@JPatrick: ไม่ใช่การบ้านธุรกิจฉันจบการศึกษา CS เมื่อหลายปีก่อน :)
Fabian Zeindl

4

ถ้าคุณเรียงลำดับรายการทั้งหมดก่อนจากนั้นคุณรับประกันเวลาเรียกใช้กรณีที่เลวร้ายที่สุด นอกจากนี้อัลกอริทึมการจัดเรียงที่คุณเลือกมีความสำคัญ

นี่คือวิธีที่ฉันจะแก้ไขปัญหานี้:

  1. ใช้การเรียงลำดับของฮีปโดยเน้นที่องค์ประกอบที่เล็กที่สุดในรายการ
  2. หลังจากการสลับแต่ละครั้งดูว่าคุณมีช่องว่างหรือไม่
  3. หากคุณพบช่องว่างแล้วreturn: คุณได้พบคำตอบของคุณ
  4. หากคุณไม่พบช่องว่างให้ทำการสลับต่อ

นี่เป็นภาพของการจัดเรียงกอง


คำถามหนึ่งคุณจะระบุองค์ประกอบ "ที่เล็กที่สุด" ของรายการได้อย่างไร
Jodrell

4

เพื่อความลึกลับและ "ฉลาด" ในกรณีพิเศษของอาเรย์ที่มี "รู" เพียงอันเดียวคุณสามารถลองใช้วิธีการแก้ปัญหาแบบ XOR:

  • กำหนดช่วงของอาร์เรย์ของคุณ สิ่งนี้ทำได้โดยการตั้งค่าตัวแปร "max" และ "min" เป็นองค์ประกอบแรกของอาร์เรย์และสำหรับแต่ละองค์ประกอบหลังจากนั้นหากองค์ประกอบนั้นน้อยกว่า min หรือมากกว่า max ให้ตั้งค่า min หรือ max เป็น ค่าใหม่
  • หากช่วงนั้นน้อยกว่าระดับความสำคัญของเซ็ตจะมี "รู" เพียงหนึ่งช่องเท่านั้นดังนั้นคุณสามารถใช้แฮคเกอร์ได้
  • เตรียมใช้งานตัวแปรจำนวนเต็ม X เป็นศูนย์
  • สำหรับแต่ละจำนวนเต็มตั้งแต่นาทีถึงสูงสุดโดยรวม XOR นั้นจะมีค่าด้วย X และเก็บผลลัพธ์ไว้ใน X
  • ตอนนี้ XOR แต่ละจำนวนเต็มในอาร์เรย์ด้วย X แล้วเก็บผลลัพธ์ต่อเนื่องกันเป็น X เหมือนเดิม
  • เมื่อคุณทำเสร็จแล้ว X จะเป็นมูลค่าของ "หลุม" ของคุณ

สิ่งนี้จะทำงานในเวลาประมาณ 2N คล้ายกับโซลูชัน bitvector แต่ต้องการพื้นที่หน่วยความจำน้อยกว่าสำหรับ N> ขนาดใด ๆ (int) อย่างไรก็ตามหากอาร์เรย์มี "หลุม" หลายตัว X จะเป็น "ผลรวม" XOR ของหลุมทั้งหมดซึ่งจะเป็นการยากหรือเป็นไปไม่ได้ที่จะแยกเป็นค่ารูที่แท้จริง ในกรณีนั้นคุณจะกลับไปใช้วิธีอื่นเช่น "pivot" หรือ "bitvector" จากคำตอบอื่น ๆ

คุณสามารถเรียกคืนสิ่งนี้ได้เช่นกันโดยใช้บางสิ่งที่คล้ายกับวิธีการหมุนเพื่อลดความซับซ้อน จัดเรียงอาร์เรย์ใหม่ตามจุดหมุน (ซึ่งจะเป็นสูงสุดของด้านซ้ายและนาทีด้านขวามันจะไม่สำคัญที่จะหาค่าสูงสุดและต่ำสุดของอาร์เรย์ในขณะหมุน) หากด้านซ้ายของเดือยมีรูหนึ่งรูหรือมากกว่าให้เจาะเข้าไปในฝั่งนั้นเท่านั้น มิฉะนั้นจะกลับเข้าสู่ด้านอื่น ๆ ณ จุดใดก็ตามที่คุณสามารถพิจารณาได้ว่ามีเพียงรูเดียวให้ใช้วิธี XOR เพื่อหามัน (ซึ่งควรจะมีราคาถูกกว่าการหมุนต่อไปเรื่อย ๆ จนถึงคอลเล็กชั่นสององค์ประกอบที่มีรูที่รู้จักซึ่งเป็นกรณีพื้นฐานสำหรับ อัลกอริธึม pivot บริสุทธิ์)


มันช่างฉลาดและยอดเยี่ยมมาก! ทีนี้คุณสามารถหาวิธีที่จะทำสิ่งนี้ด้วยจำนวนตัวแปรที่มีรู? :-D

2

ช่วงของตัวเลขที่คุณจะพบคืออะไร? หากช่วงนั้นไม่ใหญ่มากคุณสามารถแก้ปัญหานี้ด้วยการสแกนสองครั้ง (linear time O (n)) โดยใช้อาร์เรย์ที่มีองค์ประกอบมากเท่าที่คุณมีตัวเลขพื้นที่การค้าเป็นเวลา คุณสามารถค้นหาช่วงแบบไดนามิกด้วยการสแกนอีกหนึ่งครั้ง เพื่อลดพื้นที่คุณสามารถกำหนด 1 บิตให้กับแต่ละหมายเลขโดยให้คุณมีที่เก็บข้อมูล 8 หมายเลขต่อไบต์

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


1

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


1
คุณกำลังบอกว่าการสำรวจภายในรายการมีความซับซ้อนในเวลาเดียวกันกับการเรียงลำดับหรือไม่?
maple_shaft

@maple_shaft: ไม่ฉันกำลังพูดว่าการสร้างต้นไม้ไบนารีจากข้อมูลแบบสุ่มจากนั้นการสำรวจจากซ้ายไปขวาจะเทียบเท่ากับการเรียงลำดับจากนั้นข้ามจากเล็กไปใหญ่
Pillmuncher

1

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

ในมุมมองของฉันการใช้โครงสร้างฮีปอาจเป็นคำตอบทั่วไปที่สุด (โดยทั่วไปฮีปจะให้องค์ประกอบที่เล็กที่สุดได้อย่างมีประสิทธิภาพโดยไม่มีการเรียงลำดับที่สมบูรณ์)

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

อัลกอริธึมเหล่านี้ทั้งหมดมีความแข็งแกร่งขึ้นอยู่กับลักษณะการป้อนข้อมูลและข้อกำหนดของโปรแกรม


0

วิธีแก้ปัญหาที่ไม่ได้ใช้ที่เก็บข้อมูลเพิ่มเติมหรือสมมติว่าความกว้าง (32 บิต) ของจำนวนเต็ม

  1. ในหนึ่งเส้นทางผ่านค้นหาจำนวนที่น้อยที่สุด ให้เรียกสิ่งนี้ว่า "ขั้นต่ำ" ความซับซ้อนของเวลา O (n)

  2. เลือกองค์ประกอบสาระสำคัญแบบสุ่มและทำพาร์ทิชันสไตล์ quicksort

  3. หากเดือยสิ้นสุดในตำแหน่ง = ("pivot" - "min") จากนั้นให้ย่อที่ด้านขวาของพาร์ติชันมิฉะนั้นจะมีการเรียกเก็บเงินอีกครั้งทางด้านซ้ายของพาร์ติชัน แนวคิดนี้คือถ้าไม่มีรูจากจุดเริ่มต้นเดือยจะอยู่ที่ตำแหน่ง ("pivot" - "min") ดังนั้นรูแรกควรอยู่ทางด้านขวาของพาร์ติชันและในทางกลับกัน

  4. เคสพื้นฐานคืออาร์เรย์ของ 1 องค์ประกอบและรูอยู่ระหว่างองค์ประกอบนี้และอีกองค์ประกอบหนึ่ง

ความซับซ้อนของเวลาทำงานทั้งหมดที่คาดไว้คือ O (n) (8 * n พร้อมค่าคงที่) และกรณีที่แย่ที่สุดคือ O (n ^ 2) การวิเคราะห์ความซับซ้อนเวลาสำหรับปัญหาที่คล้ายกันสามารถพบได้ที่นี่


0

ฉันเชื่อว่าฉันคิดว่าสิ่งที่ควรใช้งานได้โดยทั่วไปและมีประสิทธิภาพหากคุณรับประกันว่าจะไม่ซ้ำซ้อน * (อย่างไรก็ตามควรขยายให้ครอบคลุมจำนวนหลุมและจำนวนเต็มใด ๆ )

แนวคิดเบื้องหลังวิธีนี้คล้ายกับ quicksort โดยที่เราพบเดือยและพาร์ติชั่นรอบ ๆ จากนั้นทำการรีไซด์ด้วยรูด้านข้าง หากต้องการดูว่าฝ่ายใดมีรูเราพบว่าตัวเลขต่ำสุดและสูงที่สุดและเปรียบเทียบกับเดือยและจำนวนค่าในด้านนั้น สมมติว่าเดือยคือ 17 และจำนวนขั้นต่ำคือ 11 หากไม่มีรูต้องมี 6 หมายเลข (11, 12, 13, 14, 15, 16, 17, 17) หากมี 5 เรารู้ว่ามีรูอยู่ข้างนั้นและเราสามารถเอาคืนไปหาด้านนั้นได้ ฉันมีปัญหาในการอธิบายอย่างชัดเจนยิ่งกว่านั้นดังนั้นลองมาเป็นตัวอย่าง

15 21 10 13 18 16 22 23 24 20 17 11 25 12 14

หมุน:

10 13 11 12 14 |15| 21 18 16 22 23 24 20 17 25

15 คือเดือยที่ระบุโดยไพพ์ ( ||) มีตัวเลข 5 ตัวทางด้านซ้ายของเดือยตามที่ควรมี (15 - 10) และ 9 ทางด้านขวาซึ่งควรมี 10 (25 - 15) ดังนั้นเราจึงเอาคืนทางด้านขวา; เราจะทราบว่าขอบเขตก่อนหน้านี้คือ 15 ในกรณีที่หลุมอยู่ติดกับมัน (16)

[15] 18 16 17 20 |21| 22 23 24 25

ตอนนี้มีตัวเลข 4 ตัวทางซ้าย แต่ควรมี 5 (21 - 16) ดังนั้นเราจะเรียกคืนที่นั่นและอีกครั้งเราจะทราบขอบเขตก่อนหน้านี้ (เป็นเครื่องหมายวงเล็บ)

[15] 16 17 |18| 20 [21]

ด้านซ้ายมีตัวเลข 2 ตัวที่ถูกต้อง (18 - 16) แต่ด้านขวามี 1 แทน 2 (20 - 18) ขึ้นอยู่กับเงื่อนไขสิ้นสุดของเราเราสามารถเปรียบเทียบหมายเลข 1 กับทั้งสองด้าน (18, 20) และดูว่า 19 หายไปหรือเรียกคืนอีกครั้ง:

[18] |20| [21]

ด้านซ้ายมีขนาดเป็นศูนย์โดยมีช่องว่างระหว่างเดือย (20) และขอบเขตก่อนหน้า (18) ดังนั้น 19 จึงเป็นรู

*: หากมีข้อมูลซ้ำคุณอาจใช้ชุดแฮชเพื่อลบออกในเวลา O (N) โดยใช้วิธีการทั้งหมด O (N) แต่อาจต้องใช้เวลามากกว่าวิธีอื่น


1
ฉันไม่เชื่อว่า OP พูดอะไรเกี่ยวกับการมีเพียงหลุมเดียว อินพุตเป็นรายการของตัวเลขที่ไม่เรียงลำดับซึ่งอาจเป็นอะไรก็ได้ ยังไม่ชัดเจนจากคำอธิบายของคุณวิธีที่คุณกำหนดจำนวน "มี" จำนวน
คาเลบ

@caleb ไม่สำคัญว่าจะมีกี่หลุมเพียงแค่ไม่มีรายการซ้ำ (ซึ่งสามารถลบออกใน O (N) ด้วยชุดแฮช แต่ในทางปฏิบัติอาจมีค่าใช้จ่ายมากกว่าวิธีอื่น ๆ ) ฉันพยายามปรับปรุงคำอธิบายดูว่าดีกว่านี้ไหม
เควิน

นี่ไม่ใช่เส้นตรง IMO มันเป็นเหมือน (logN) ^ 2 ในแต่ละขั้นตอนคุณหมุนชุดย่อยของคอลเลกชันที่คุณสนใจ (ครึ่งหนึ่งของ subarray ก่อนหน้านี้ซึ่งคุณระบุว่ามี "หลุม" แรก) จากนั้นเรียกคืนทางด้านซ้ายหากมี "หลุม" หรือด้านขวาถ้าด้านซ้ายไม่ได้ (logN) ^ 2 ยังดีกว่าแบบเส้นตรง ถ้า N เพิ่มขึ้นเป็นสิบเท่าคุณจะทำตามลำดับที่ 2 (บันทึก (N) -1) + 1 ขั้นตอนเพิ่มเติม
KeithS

@ Keith - น่าเสียดายที่คุณต้องดูตัวเลขทั้งหมดในแต่ละระดับเพื่อหมุนพวกมันดังนั้นมันจะใช้เวลาประมาณ n + n / 2 + n / 4 + ... = 2n (ทางเทคนิค, 2 (nm)) .
เควิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.