สร้างจำนวนเต็มที่ไม่ใช่หนึ่งในสี่พันล้านที่ได้รับ


692

ฉันได้รับคำถามสัมภาษณ์นี้:

ให้อินพุตไฟล์ที่มีจำนวนเต็มสี่พันล้านชุดให้อัลกอริทึมในการสร้างจำนวนเต็มซึ่งไม่ได้อยู่ในไฟล์ สมมติว่าคุณมีหน่วยความจำ 1 GB ติดตามสิ่งที่คุณจะทำถ้าคุณมีหน่วยความจำเพียง 10 MB

การวิเคราะห์ของฉัน:

ขนาดของไฟล์คือ 4 × 10 9 × 4 ไบต์ = 16 GB

เราสามารถทำการจัดเรียงภายนอกได้ดังนั้นจึงแจ้งให้เราทราบช่วงของจำนวนเต็ม

คำถามของฉันคือวิธีที่ดีที่สุดในการตรวจจับจำนวนเต็มหายไปในชุดจำนวนเต็มเรียงลำดับขนาดใหญ่?

ความเข้าใจของฉัน (หลังจากอ่านคำตอบทั้งหมด):

สมมติว่าเรากำลังพูดถึงจำนวนเต็ม 32 บิตมี 2 32 = 4 * 10 9จำนวนเต็มชัดเจน

กรณีที่ 1: เรามี 1 GB = 1 * 10 9 * 8 บิต = หน่วยความจำ 8 พันล้านบิต

สารละลาย:

ถ้าเราใช้หนึ่งบิตที่เป็นตัวแทนของจำนวนเต็มจำนวนหนึ่งก็เพียงพอแล้ว เราไม่ต้องการการจัดเรียง

การดำเนินงาน:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

กรณีที่ 2: หน่วยความจำ 10 MB = 10 * 10 6 * 8 บิต = 80 ล้านบิต

สารละลาย:

สำหรับคำนำหน้า 16- บิตที่เป็นไปได้ทั้งหมดมีจำนวนเต็ม16 16 = 65536 เราต้องการ 2 16 * 4 * 8 = 2 ล้านบิต เราต้องการสร้างถัง 65536 สำหรับที่เก็บข้อมูลแต่ละชุดเราจำเป็นต้องมี 4 ไบต์เพื่อเก็บความเป็นไปได้ทั้งหมดเนื่องจากกรณีที่เลวร้ายที่สุดคือจำนวนเต็ม 4 พันล้านทั้งหมดที่อยู่ในที่เก็บข้อมูลชุดเดียวกัน

  1. สร้างตัวนับของที่เก็บข้อมูลแต่ละชุดผ่านการส่งผ่านครั้งแรกผ่านไฟล์
  2. สแกนถังหาคนแรกที่มีน้อยกว่า 65536 ครั้ง
  3. สร้างที่เก็บข้อมูลใหม่ซึ่งเราพบว่ามีคำนำหน้าสูง 16 บิตในขั้นตอนที่ 2 ถึงการส่งผ่านครั้งที่สองของไฟล์
  4. สแกนที่เก็บข้อมูลที่สร้างในขั้นตอนที่ 3 ค้นหาที่เก็บข้อมูลชุดแรกซึ่งไม่มีการเข้าชม

รหัสนี้คล้ายกับรหัสด้านบน

สรุป: เราลดหน่วยความจำผ่านการเพิ่มไฟล์ผ่าน


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


32
@trashgod ผิด สำหรับจำนวนเต็ม 4294967295 ที่ไม่ซ้ำคุณจะมีจำนวนเต็ม 1 ตัว หากต้องการค้นหาคุณควรสรุปจำนวนเต็มทั้งหมดและย่อส่วนจากการสรุปที่คำนวณล่วงหน้าของจำนวนเต็มที่เป็นไปได้ทั้งหมด
Nakilon

58
นี่คือ "ไข่มุก" อันที่สองจาก "การเขียนโปรแกรมไข่มุก" และฉันขอแนะนำให้คุณอ่านการอภิปรายทั้งหมดในหนังสือ ดูbooks.google.co.th/…
Alok Singhal

8
@ ริชาร์ด int 64 บิตจะมากกว่าขนาดใหญ่พอ
cftarnas

79
int getMissingNumber(File inputFile) { return 4; }( อ้างอิง )
johnny

14
ไม่สำคัญว่าคุณจะไม่สามารถเก็บผลรวมของจำนวนเต็มทั้งหมดจาก 1 ถึง 2 ^ 32 ได้เนื่องจากประเภทจำนวนเต็มในภาษาเช่น C / C ++ มักจะรักษาคุณสมบัติเช่นการเชื่อมโยงและการสื่อสาร สิ่งนี้หมายความว่าแม้ว่าผลรวมจะไม่เป็นคำตอบที่ถูกต้องหากคุณคำนวณผลรวมที่คาดหวังด้วยผลรวมที่เกิดขึ้นจริงกับผลรวมล้นแล้วลบผลที่ได้จะยังคงถูกต้อง
thedayturns

คำตอบ:


530

สมมติว่า "จำนวนเต็ม" หมายถึง 32 บิต : พื้นที่ว่าง 10 MB นั้นเพียงพอสำหรับคุณที่จะนับว่ามีตัวเลขจำนวนเท่าใดในไฟล์อินพุตที่มีส่วนนำหน้า 16 บิตใด ๆ สำหรับคำนำหน้า 16 บิตที่เป็นไปได้ทั้งหมดในครั้งเดียวผ่าน ไฟล์อินพุต อย่างน้อยหนึ่งถังจะถูกโจมตีน้อยกว่า 2 16ครั้ง ใช้รหัสผ่านที่สองเพื่อค้นหาว่ามีหมายเลขใดบ้างที่เป็นไปได้ในที่ฝากข้อมูลนั้นแล้ว

ถ้ามันมีความหมายมากกว่า 32 บิต แต่ยังคงมีขนาดที่ จำกัด : ทำตามข้างบนโดยไม่สนใจหมายเลขอินพุตทั้งหมดที่อยู่นอกช่วง (ลงนามหรือไม่ได้ลงนาม; ตัวเลือกของคุณ) ช่วง 32 บิต

หาก "จำนวนเต็ม" หมายถึงจำนวนเต็มทางคณิตศาสตร์ : อ่านผ่านอินพุตหนึ่งครั้งและติดตามความยาวจำนวนที่มากที่สุดของจำนวนที่ยาวที่สุดที่คุณเคยเห็น เมื่อคุณทำเสร็จแล้วให้ส่งออกสูงสุดบวกหนึ่งหมายเลขสุ่มที่มีหนึ่งหลัก (หนึ่งในตัวเลขในไฟล์อาจเป็น bignum ที่ใช้เวลามากกว่า 10 MB เพื่อแสดงอย่างแน่นอน แต่หากอินพุตเป็นไฟล์อย่างน้อยคุณก็สามารถแสดงความยาวของสิ่งใดก็ได้ที่อยู่ในนั้น)


24
สมบูรณ์ คำตอบแรกของคุณต้องการเพียง 2 ผ่านไฟล์!
corsiKa

47
Bignum ขนาด 10 MB? มันค่อนข้างสุดขีด
Mark Ransom

12
@Legate เพียงข้ามตัวเลขที่มากเกินไปและไม่ทำอะไรเลย เนื่องจากคุณจะไม่ส่งออกจำนวนมากเกินไปดังนั้นคุณไม่จำเป็นต้องติดตามว่าคุณเห็นรายการใด
hmakholm ออกเดินทางจากโมนิกา

12
สิ่งที่ดีเกี่ยวกับโซลูชันที่ 1 คือคุณสามารถลดหน่วยความจำโดยการเพิ่มผ่าน
Yousf

11
@Barry: คำถามข้างต้นไม่ได้ระบุว่ามีหมายเลขหนึ่งที่ขาดหายไปอย่างแน่นอน ไม่ได้บอกว่าตัวเลขในไฟล์ไม่ซ้ำกัน (ตามคำถามที่ถามจริงอาจเป็นความคิดที่ดีในการสัมภาษณ์ใช่มั้ย?)
Christopher Creutzig

197

อัลกอริทึมที่ได้รับข้อมูลเชิงสถิติแก้ปัญหานี้โดยใช้การผ่านที่น้อยกว่าวิธีการที่กำหนดไว้

หากอนุญาตให้มีจำนวนเต็มมากจะทำให้สามารถสร้างตัวเลขที่น่าจะไม่ซ้ำกันในเวลา O (1) หลอกสุ่มจำนวนเต็ม 128 บิตเช่นGUIDจะมีเพียงหนึ่งชนกับของที่มีอยู่สี่พันล้านจำนวนเต็มในชุดในเวลาน้อยกว่าหนึ่งจากทุก 64000000000 พันล้านพันล้านกรณี

หากจำนวนเต็มถูก จำกัด ไว้ที่ 32 บิตแล้วหนึ่งสามารถสร้างตัวเลขที่น่าจะไม่ซ้ำกันในรอบเดียวโดยใช้น้อยกว่า 10 MB อัตราต่อรองที่เป็นจำนวนเต็ม 32 บิตหลอกเทียมจะชนกับหนึ่งใน 4 พันล้านที่มีอยู่คือประมาณ 93% (4e9 / 2 ^ 32) อัตราต่อรองที่ 1,000 จำนวนเต็มหลอกหลอกทั้งหมดจะชนกันน้อยกว่าหนึ่งใน 12,000 ล้านล้านล้าน (อัตราต่อรองของการปะทะกัน ^ 1000) ดังนั้นหากโปรแกรมรักษาโครงสร้างข้อมูลที่มีตัวเลือกสุ่มหลอก 1,000 ตัวและทำซ้ำผ่านจำนวนเต็มที่รู้จักการกำจัดการจับคู่จากผู้สมัครมันเป็นเพียงการค้นหาอย่างน้อยหนึ่งจำนวนเต็มที่ไม่ได้อยู่ในไฟล์


32
ฉันค่อนข้างแน่ใจว่าจำนวนเต็มถูก จำกัด หากไม่เป็นเช่นนั้นแม้แต่โปรแกรมเมอร์มือใหม่ก็ยังนึกถึงอัลกอริธึม "นำหนึ่งผ่านข้อมูลเพื่อค้นหาจำนวนสูงสุดแล้วบวก 1 เข้าไป"
Adrian Petrescu

12
การคาดเดาที่แท้จริงว่าการสุ่มเอาท์พุทอาจไม่ทำให้คุณได้คะแนนมากในการสัมภาษณ์
Brian Gordon

6
@ เอเดรียทางออกของคุณดูเหมือนชัดเจน (และสำหรับฉันฉันใช้มันในคำตอบของตัวเอง) แต่ก็ไม่ชัดเจนสำหรับทุกคน เป็นการทดสอบที่ดีเพื่อดูว่าคุณสามารถเห็นวิธีแก้ไขปัญหาที่ชัดเจนหรือไม่หรือว่าคุณจะยุ่งยากทุกอย่างที่คุณสัมผัส
Mark Ransom

19
@Brian: ฉันคิดว่าวิธีนี้มีทั้งความคิดสร้างสรรค์และในทางปฏิบัติ ฉันคนเดียวจะให้ความรุ่งโรจน์มากสำหรับคำตอบนี้
Richard H

6
ออยู่ที่นี่เส้นแบ่งระหว่างวิศวกรและนักวิทยาศาสตร์ คำตอบที่ยอดเยี่ยมเบ็น!
TrojanName

142

การอภิปรายอย่างละเอียดเกี่ยวกับปัญหานี้ได้รับการกล่าวถึงในJon Bentley "คอลัมน์ 1 แคร็กเดอะออยสเตอร์" การเขียนโปรแกรม Pearls Addison-Wesley pp.3-10

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

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

รหัสที่จะใช้บิตเซ็ตนั้นง่ายมาก: (นำมาจากหน้าโซลูชัน )

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

อัลกอริทึมของเบนท์ลีย์ทำการส่งผ่านไฟล์ไฟล์เดียวโดยsetใช้บิตที่เหมาะสมในอาร์เรย์จากนั้นตรวจสอบอาร์เรย์นี้โดยใช้testมาโครด้านบนเพื่อค้นหาหมายเลขที่หายไป

หากหน่วยความจำที่มีอยู่น้อยกว่า 0.466 GB, Bentley แนะนำอัลกอริทึม k-pass ซึ่งแบ่งอินพุตเป็นช่วงขึ้นอยู่กับหน่วยความจำที่มีอยู่ เพื่อยกตัวอย่างง่ายๆมากถ้ามีเพียง 1 ไบต์ (เช่นหน่วยความจำเพื่อจัดการตัวเลข 8 ตัว) และมีช่วงตั้งแต่ 0 ถึง 31 เราแบ่งสิ่งนี้เป็น 0 ถึง 7, 8-15, 16-22 และอื่น ๆ และจัดการช่วงนี้ในแต่ละ32/8 = 4รอบ

HTH


12
ฉันไม่รู้หนังสือ แต่ก็ไม่มีเหตุผลที่จะเรียกมันว่า "Wonder Sort" เพราะมันเป็นเพียงแค่ถังเล็ก ๆ ที่มีเคาน์เตอร์ 1 บิต
flolo

3
แม้ว่าพกพามากขึ้นรหัสนี้จะถูกทำลายด้วยรหัสที่เขียนขึ้นเพื่อใช้ประโยชน์จากคำแนะนำเวกเตอร์ฮาร์ดแวร์สนับสนุน ฉันคิดว่า gcc สามารถแปลงรหัสโดยอัตโนมัติเพื่อใช้การดำเนินการเวกเตอร์ในบางกรณีแม้ว่า
Brian Gordon

3
@ ไบรอันฉันไม่คิดว่าจอนเบนท์ลีย์อนุญาตให้สิ่งต่าง ๆ ในหนังสือของเขาเกี่ยวกับอัลกอริทึม
David Heffernan

8
@BrianGordon เวลาที่ใช้ใน ram จะน้อยมากเมื่อเทียบกับเวลาที่ใช้ในการอ่านไฟล์ ลืมเรื่องการปรับให้เหมาะสม
เอียน

1
@BrianGordon: หรือคุณพูดถึงวงในตอนท้ายเพื่อค้นหาบิตแรก ใช่เว็กเตอร์จะเร่งความเร็วขึ้น แต่วนไปตามบิตฟิลด์ด้วยจำนวนเต็ม 64 บิตมองหาหนึ่งที่!= -1จะยังคงแบนด์วิดท์ของหน่วยความจำที่อิ่มตัวที่ทำงานบนแกนหลักเดียว (สำหรับการออกแบบ Intel / AMD ล่าสุด) คุณต้องคิดออกว่าบิตใดที่ไม่ได้ตั้งค่าหลังจากที่คุณพบตำแหน่ง 64 บิตที่มีอยู่ (และสำหรับสิ่งที่คุณทำได้not / lzcnt) จุดประสงค์ที่การวนลูปมากกว่าการทดสอบบิตเดียวอาจไม่ได้รับการปรับให้เหมาะสม
Peter Cordes

120

เนื่องจากปัญหาไม่ได้ระบุว่าเราต้องค้นหาจำนวนที่น้อยที่สุดที่ไม่ได้อยู่ในไฟล์เราจึงสามารถสร้างตัวเลขที่ยาวกว่าไฟล์อินพุตได้ :)


6
เว้นแต่ว่าจำนวนที่มากที่สุดในไฟล์คือจำนวนสูงสุดแล้วคุณก็จะล้น
KBusc

ขนาดของไฟล์นั้นในโปรแกรมโลกแห่งความจริงที่อาจต้องสร้างจำนวนเต็มใหม่และผนวกเข้ากับไฟล์ "จำนวนเต็มที่ใช้" 100 ครั้ง?
Michael

2
ฉันคิดอย่างนี้ สมมติว่าintเป็นบิตเพียงเอาท์พุท32 2^64-1เสร็จสิ้น
imallett

1
ถ้าเป็นหนึ่ง int ต่อบรรทัดtr -d '\n' < nums.txt > new_num.txt: D
Shon

56

สำหรับตัวแปร RAM ขนาด 1 GB คุณสามารถใช้บิตเวกเตอร์ได้ คุณต้องจัดสรร 4 พันล้านบิต == 500 MB ไบต์อาร์เรย์ สำหรับแต่ละหมายเลขที่คุณอ่านจากอินพุตให้ตั้งค่าบิตที่สอดคล้องกันเป็น '1' เมื่อคุณทำเสร็จแล้ววนซ้ำบิตค้นหาครั้งแรกที่ยังคง '0' ดัชนีของมันคือคำตอบ


4
ไม่ได้ระบุช่วงของตัวเลขในอินพุต อัลกอริทึมนี้ทำงานอย่างไรถ้าอินพุตประกอบด้วยตัวเลขคู่ทั้งหมดระหว่าง 8,000 ถึง 16 พันล้าน?
Mark Ransom

27
@ Mark เพียงเพิกเฉยอินพุตที่อยู่นอกช่วง 0..2 ^ 32 คุณจะไม่ส่งออกใด ๆ ของพวกเขาต่อไปดังนั้นจึงไม่จำเป็นต้องจำที่ควรหลีกเลี่ยง
hmakholm ออกเดินทางจากโมนิกา

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

4
แทนที่จะวนซ้ำตัวเองคุณสามารถใช้bitSet.nextClearBit(0): download.oracle.com/javase/6/docs/api/java/util/
......

3
มันจะมีประโยชน์ที่จะกล่าวถึงว่าโดยไม่คำนึงถึงช่วงของจำนวนเต็มอย่างน้อยหนึ่งบิตรับประกันว่าจะเป็น 0 ในตอนท้ายของการผ่าน นี่เป็นเพราะหลักการของนกพิราบ
Rafał Dowgird

46

หากพวกเขาเป็นจำนวนเต็ม 32- บิต (น่าจะมาจากตัวเลือกของ ~ 4 พันล้านหมายเลขใกล้กับ 2 32 ) รายการของคุณ 4 พันล้านหมายเลขจะใช้เวลามากที่สุด 93% ของจำนวนเต็มที่เป็นไปได้ (4 * 10 9 / (2 32 ) ) ดังนั้นถ้าคุณสร้างบิตอาเรย์ของ 2 32บิตโดยแต่ละบิตเริ่มต้นเป็นศูนย์ (ซึ่งจะใช้เวลา 2 29ไบต์ถึง 500 เมกะไบต์แรม; จำไบต์ = 2 3บิต = 8 บิต) อ่านรายการจำนวนเต็มของคุณและ สำหรับแต่ละ int ตั้งองค์ประกอบบิต - อาเรย์ที่สอดคล้องกันจาก 0 ถึง 1; จากนั้นอ่านบิตเรตของคุณแล้วส่งกลับบิตแรกที่ยังคงเป็น 0

ในกรณีที่คุณมี RAM น้อยกว่า (~ 10 MB) โซลูชันนี้จะต้องแก้ไขเล็กน้อย 10 MB ~ 83886080 บิตยังคงเพียงพอที่จะทำบิตเรย์สำหรับตัวเลขทั้งหมดระหว่าง 0 ถึง 83886079 ดังนั้นคุณสามารถอ่านรายการ int ของคุณ และบันทึกเฉพาะ #s ที่อยู่ระหว่าง 0 ถึง 83886079 ในอาร์เรย์บิตของคุณ หากตัวเลขมีการกระจายแบบสุ่ม; ด้วยความน่าจะเป็นที่ล้นหลาม (มันแตกต่างกัน 100% ประมาณ10 -2592069 ) คุณจะพบ int ที่ขาดหายไป) ในความเป็นจริงถ้าคุณเลือกตัวเลข 1 ถึง 2048 (มี RAM 256 ไบต์เท่านั้น) คุณยังคงพบว่าจำนวนที่ขาดหายไปนั้นเป็นเปอร์เซ็นต์ที่น่าเหลือเชื่อ (99.9999999999999999999999999999999999999999999999999999999999999999999999999999999999

แต่สมมุติว่าแทนที่จะมีตัวเลขประมาณ 4 พันล้านตัว คุณมีบางอย่างเช่น 2 32 - 1 ตัวเลขและ RAM น้อยกว่า 10 MB ดังนั้นช่วงเล็ก ๆ ของ ints นั้นมีความเป็นไปได้เพียงเล็กน้อยที่ไม่ได้มีจำนวน

หากคุณรับประกันว่าแต่ละ int ในรายการนั้นไม่ซ้ำกันคุณสามารถหาผลรวมของตัวเลขและลบผลรวมด้วยจำนวน # ที่หายไปกับผลรวมทั้งหมด (½) (2 32 ) (2 32 - 1) = 9223372034707292160 เพื่อหา int ที่ขาดหายไป . อย่างไรก็ตามหากเกิด int ขึ้นสองครั้งวิธีนี้จะล้มเหลว

อย่างไรก็ตามคุณสามารถแบ่งและพิชิตได้เสมอ วิธีการที่ไร้เดียงสาคืออ่านอาร์เรย์และนับจำนวนตัวเลขที่อยู่ในครึ่งแรก (0 ถึง 2 31 -1) และครึ่งหลัง (2 31 , 2 32 ) จากนั้นเลือกช่วงที่มีตัวเลขน้อยลงและแบ่งช่วงนั้นซ้ำครึ่ง (พูดว่าถ้ามีจำนวนน้อยกว่าสองใน (2 31 , 2 32 ) ดังนั้นการค้นหาครั้งต่อไปของคุณจะนับจำนวนในช่วง (2 31 , 3 * 2 30 -1), (3 * 2 30 , 2 32 ) ทำซ้ำจนกว่าคุณจะพบช่วงที่มีตัวเลขเป็นศูนย์และคุณมีคำตอบของคุณควรใช้ O (lg N) ~ 32 อ่านผ่านอาร์เรย์

วิธีการนั้นไม่มีประสิทธิภาพ เราใช้จำนวนเต็มสองตัวเท่านั้นในแต่ละขั้นตอน (หรือประมาณ 8 ไบต์ของ RAM พร้อมด้วยจำนวนเต็ม 4 ไบต์ (32- บิต)) วิธีที่ดีกว่าคือการแบ่งออกเป็น sqrt (2 32 ) = 2 16 = 65536 bins แต่ละอันมี 65536 หมายเลขในถังขยะ แต่ละ bin ต้องการ 4 ไบต์เพื่อเก็บจำนวนของมันดังนั้นคุณต้องมี 2 18ไบต์ = 256 kB ดังนั้น bin 0 คือ (0 ถึง 65535 = 2 16 -1), bin 1 คือ (2 16 = 65536 ถึง 2 * 2 16 -1 = 131071), bin 2 คือ (2 * 2 16 = 131072 ถึง 3 * 2 16 - 1 = 196607) ในงูหลามคุณจะมีสิ่งที่ชอบ:

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

อ่านรายการจำนวนเต็ม ~ 4 พันล้าน; และนับจำนวน ints ที่อยู่ในแต่ละ 2 16ถังขยะและค้นหา incomplete_bin ที่ไม่มีหมายเลขทั้งหมด 65536 จากนั้นคุณอ่านจำนวนเต็ม 4 พันล้านรายการอีกครั้ง แต่คราวนี้สังเกตได้ก็ต่อเมื่อจำนวนเต็มอยู่ในช่วงนั้น พลิกบิตเมื่อคุณพบพวกเขา

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break

3
ช่างเป็นคำตอบที่ยอดเยี่ยมมาก มันจะใช้งานได้จริง และได้รับประกันผลลัพธ์
Jonathan Dickinson

@dr jimbob จะทำอย่างไรถ้ามีเพียงหนึ่งหมายเลขในถังขยะและหมายเลขเดียวนั้นมีซ้ำ 65535 รายการ หากเป็นเช่นนั้นถังขยะจะยังคงนับ 65536 แต่ตัวเลขทั้งหมด 65536 เป็นหมายเลขเดียวกัน
Alcott

@Alcott - ฉันคิดว่าคุณมีตัวเลข 2 ^ 32-1 (หรือน้อยกว่า) ดังนั้นตามหลักการของช่องสำหรับพิราบคุณรับประกันได้ว่าจะมีหนึ่งถังที่มีน้อยกว่า 65536 นับเพื่อตรวจสอบรายละเอียดเพิ่มเติม เรากำลังพยายามหาจำนวนเต็มหนึ่งตัวที่ขาดหายไปไม่ใช่ทั้งหมด หากคุณมีตัวเลข 2 ^ 32 หรือมากกว่าคุณจะไม่สามารถรับประกันจำนวนเต็มที่ขาดหายไปและจะไม่สามารถใช้วิธีนี้ได้ (หรือมีการรับประกันตั้งแต่ต้นมีจำนวนเต็มหายไป) ทางออกที่ดีที่สุดของคุณก็คือกำลังดุร้าย (เช่นอ่านอาร์เรย์ 32 ครั้งตรวจสอบ 65536 #s ครั้งแรกในครั้งแรกและหยุดเมื่อพบคำตอบ)
dr jimbob

ฉลาดบน-16 / วิธีการลด-16 ถูกโพสต์ก่อนหน้านี้โดยเฮนนิ่ง: stackoverflow.com/a/7153822/224132 ฉันชอบความคิดที่เพิ่มเข้ามาสำหรับชุดจำนวนเต็มที่ไม่ซ้ำกันซึ่งขาดสมาชิกหนึ่งคน
Peter Cordes

3
@ PeterCordes - ใช่ทางออกของ Henning ถือกำเนิดขึ้นมาก่อนหน้านี้ แต่ฉันคิดว่าคำตอบของฉันยังคงมีประโยชน์ (ทำงานหลายอย่างโดยละเอียด) ที่กล่าวว่า Jon Bentley ในหนังสือของเขา Programming Pearls เสนอทางเลือกแบบมัลติพาสสำหรับปัญหานี้ (ดูคำตอบของเถาวัลย์) ก่อนที่จะมี stackoverflow อยู่ (ไม่ใช่ว่าฉันอ้างว่าเราขโมยทั้งสองอย่างโดยเจตนา วิเคราะห์ปัญหานี้ - มันเป็นทางออกที่เป็นธรรมชาติในการพัฒนา) การผ่านสองครั้งนั้นดูเป็นธรรมชาติที่สุดเมื่อข้อ จำกัด คือคุณไม่มีหน่วยความจำเพียงพอสำหรับโซลูชัน 1 รอบที่มีอาร์เรย์บิตขนาดยักษ์
dr jimbob

37

ทำไมมันซับซ้อน? คุณถามจำนวนเต็มไม่ได้อยู่ในไฟล์?

ตามกฎที่ระบุสิ่งเดียวที่คุณต้องเก็บคือจำนวนเต็มที่มากที่สุดที่คุณพบในไฟล์ เมื่ออ่านไฟล์ทั้งหมดแล้วให้ส่งคืนหมายเลข 1 ที่มากกว่านั้น

ไม่มีความเสี่ยงในการกดปุ่ม maxint หรืออะไรก็ตามเนื่องจากตามกฎแล้วไม่มีการ จำกัด ขนาดของจำนวนเต็มหรือจำนวนที่ส่งคืนโดยอัลกอริทึม


4
นี้จะทำงานเว้นแต่ int สูงสุดอยู่ในแฟ้มที่เป็นไปได้ทั้งหมด ...
pearsonartphoto

13
กฎไม่ได้ระบุว่าเป็น 32 บิตหรือ 64 บิตหรืออะไรก็ตามดังนั้นตามกฎที่ระบุไว้จึงไม่มี max int จำนวนเต็มไม่ใช่คำศัพท์คอมพิวเตอร์ แต่เป็นคำศัพท์ทางคณิตศาสตร์ที่ระบุจำนวนเต็มบวกหรือลบ
Pete

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

24
ความคิดทั้งหมดของ "max int" ไม่ถูกต้องในบริบทหากไม่มีการระบุภาษาการเขียนโปรแกรม ดูที่นิยามของ Python เกี่ยวกับจำนวนเต็มแบบยาว มันไร้ขีด จำกัด ไม่มีหลังคา คุณสามารถเพิ่มได้ตลอดเวลา คุณกำลังสมมติว่ากำลังใช้งานในภาษาที่มีค่าสูงสุดที่อนุญาตสำหรับจำนวนเต็ม
Pete

32

สิ่งนี้สามารถแก้ไขได้ในพื้นที่น้อยมากโดยใช้ชุดการค้นหาแบบไบนารี

  1. เริ่มต้นด้วยช่วงที่ได้รับอนุญาตของตัวเลขที่จะ04294967295

  2. คำนวณจุดกึ่งกลาง

  3. วนดูไฟล์นับจำนวนเท่ากันน้อยกว่าหรือสูงกว่าค่าจุดกึ่งกลาง

  4. ถ้าไม่มีตัวเลขเท่ากันคุณก็ทำเสร็จแล้ว ตัวเลขจุดกึ่งกลางคือคำตอบ

  5. หรือเลือกช่วงที่มีตัวเลขน้อยที่สุดและทำซ้ำจากขั้นตอนที่ 2 ด้วยช่วงใหม่นี้

สิ่งนี้จะต้องสแกนเชิงเส้นสูงสุดถึง 32 ไฟล์ แต่จะใช้หน่วยความจำเพียงไม่กี่ไบต์ในการจัดเก็บช่วงและจำนวน

นี่เป็นหลักเหมือนกับโซลูชันของ Henningยกเว้นว่าจะใช้ถังขยะสองถังแทน 16k


2
มันเป็นสิ่งที่ฉันเริ่มต้นด้วยก่อนที่ฉันจะเริ่มปรับพารามิเตอร์ให้เหมาะสม
hmakholm ออกเดินทางจากโมนิกา

@ Henning: เจ๋ง มันเป็นตัวอย่างที่ดีของอัลกอริธึมที่ปรับเปลี่ยนการแลกเปลี่ยนเวลาว่างได้ง่าย
ไวยากรณ์

@hammar แต่ถ้ามีตัวเลขที่ปรากฏมากกว่าหนึ่งครั้งล่ะ?
Alcott

@Alcott: อัลกอริธึมจะเลือก bin ที่หนาแน่นแทนที่จะเป็น sparser bin แต่ด้วยหลักการของ pigeonhole มันไม่สามารถเลือก bin เต็มได้เลย (จำนวนที่น้อยกว่าของการนับทั้งสองจะน้อยกว่าช่วงของ bin เสมอ)
Peter Cordes

27

แก้ไขตกลงไม่คิดค่อนข้างผ่านมันถือว่าเป็นจำนวนเต็มในไฟล์ตามการกระจายคงที่ เห็นได้ชัดว่าพวกเขาไม่จำเป็นต้องทำ แต่ถึงอย่างนั้นก็ควรลอง:


มีจำนวนเต็ม≈4.3พันล้าน 32- บิต เราไม่ทราบว่ามีการแจกจ่ายไฟล์อย่างไร แต่กรณีที่เลวร้ายที่สุดคือกรณีที่มีเอนโทรปีของแชนนอนสูงที่สุด: การกระจายเท่ากัน ในกรณีนี้ความน่าจะเป็นสำหรับเลขจำนวนเต็มหนึ่งค่าที่ไม่เกิดขึ้นในไฟล์คือ

((2³²-1) / 2³²) ⁴⁰⁰⁰⁰⁰⁰⁰⁰⁰≈ .4

ยิ่งค่าเอนโทรปีของแชนนอนลดลงความน่าจะเป็นนี้จะสูงขึ้นโดยเฉลี่ย แต่สำหรับกรณีที่แย่ที่สุดนี้เรามีโอกาส 90% ที่จะหาจำนวนที่ไม่เกิดขึ้นหลังจาก 5 เดาด้วยจำนวนเต็มแบบสุ่ม เพียงแค่สร้างหมายเลขดังกล่าวด้วยเครื่องกำเนิดไฟฟ้าเทียมเทียมเก็บไว้ในรายการ จากนั้นอ่าน int หลังจาก int และเปรียบเทียบกับการคาดเดาทั้งหมดของคุณ เมื่อมีการแข่งขันให้ลบรายการนี้ หลังจากผ่านไฟล์ทั้งหมดไปแล้วคุณจะมีโอกาสเหลืออีกหนึ่งครั้ง ใช้ใด ๆ ของพวกเขา ในกรณีที่หายาก (10% แม้ในกรณีที่เลวร้ายที่สุด) ที่ไม่มีการเดาเหลือให้รับชุดจำนวนเต็มแบบสุ่มใหม่ซึ่งอาจมากกว่าครั้งนี้ (10> 99%)

การใช้หน่วยความจำ: สองสามสิบไบต์ความซับซ้อน: O (n) ค่าใช้จ่าย: ไม่สามารถรวบรวมได้เนื่องจากส่วนใหญ่จะใช้เวลาในการเข้าถึงฮาร์ดดิสก์ที่หลีกเลี่ยงไม่ได้แทนที่จะเปรียบเทียบ ints อยู่ดี


กรณีที่เลวร้ายที่สุดที่เกิดขึ้นจริงเมื่อเราไม่ถือว่าการแจกแจงแบบคงที่คือจำนวนเต็มทุกตัวเกิดขึ้นสูงสุด หนึ่งครั้งเพราะเพียง 1 - 4000000000 / 2³²≈ 6% ของจำนวนเต็มทั้งหมดจะไม่เกิดขึ้นในไฟล์ ดังนั้นคุณจะต้องเดาเพิ่มเติม แต่ก็ยังไม่เสียค่าใช้จ่ายจำนวนหน่วยความจำที่เป็นอันตราย


5
ฉันดีใจที่เห็นคนอื่นคิดเกี่ยวกับเรื่องนี้ แต่ทำไมถึงลงที่นี่ที่ด้านล่าง นี่คือ algo 1 pass … 10 MB เพียงพอสำหรับการคาดเดา 2.5 M และ 93% ^ 2.5M ≈ 10 ^ -79000 เป็นโอกาสที่สำคัญอย่างยิ่งในการสแกนครั้งที่สอง เนื่องจากค่าใช้จ่ายในการค้นหาแบบไบนารีจะเร็วขึ้นหากคุณใช้การเดาน้อยลง! สิ่งนี้เหมาะสมที่สุดทั้งเวลาและสถานที่
Potatoswatter

1
@Potatoswatter: ดีที่คุณพูดถึงการค้นหาแบบไบนารี นั่นอาจไม่คุ้มกับค่าใช้จ่ายเมื่อใช้การเดาเพียง 5 ครั้ง แต่แน่นอนว่าอยู่ที่ 10 หรือมากกว่า คุณสามารถคาดเดา 2 M ได้ แต่จากนั้นคุณควรเก็บไว้ในชุดแฮชเพื่อรับ O (1) สำหรับการค้นหา
leftaroundabout

1
@Potatoswatter คำตอบที่เทียบเท่าของ Ben Haley อยู่ใกล้จุดสูงสุด
Brian Gordon

1
ฉันชอบวิธีการนี้ แต่จะแนะนำการปรับปรุงการประหยัดหน่วยความจำ: ถ้ามีหน่วยเก็บข้อมูลที่มีดัชนีบิต N รวมทั้งหน่วยความจำคงที่บางอย่างกำหนดฟังก์ชั่น scrambling 32 บิตย้อนกลับได้ที่กำหนดค่าได้ บิตที่จัดทำดัชนี จากนั้นอ่านแต่ละหมายเลขจากไฟล์จากนั้นเบียดเสียดมันและถ้าผลลัพธ์น้อยกว่า N ให้ตั้งค่าบิตที่สอดคล้องกัน หากไม่ได้ตั้งค่าบิตใด ๆ ไว้ท้ายไฟล์ให้ย้อนกลับฟังก์ชันการช่วงชิงบนดัชนี ด้วยหน่วยความจำ 64KB หนึ่งสามารถทดสอบได้อย่างมีประสิทธิภาพกว่า 512,000 หมายเลขสำหรับความพร้อมใช้งานในครั้งเดียว
supercat

2
แน่นอนด้วยอัลกอริทึมนี้กรณีที่แย่ที่สุดคือตัวเลขที่สร้างขึ้นโดยตัวสร้างตัวเลขสุ่มตัวเดียวกับที่คุณใช้ สมมติว่าคุณสามารถรับประกันได้ว่าไม่ใช่กรณีกลยุทธ์ที่ดีที่สุดของคุณคือการใช้ตัวสร้างตัวเลขสุ่มเชิงเส้นเพื่อสร้างรายชื่อของคุณเพื่อให้คุณผ่านพื้นที่จำนวนในทางหลอกเทียม นั่นหมายความว่าหากคุณล้มเหลวคุณสามารถสร้างตัวเลขต่อไปได้จนกว่าคุณจะได้ครอบคลุมช่วงทั้งหมดของ ints (จากที่ได้พบช่องว่าง) โดยไม่ต้องทำซ้ำความพยายามของคุณ
Dewi Morgan

25

หากคุณมีจำนวนเต็มหนึ่งตัวที่หายไปจากช่วง [0, 2 ^ x - 1] จากนั้นก็ให้ xor ทั้งหมดเข้าด้วยกัน ตัวอย่างเช่น:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(ฉันรู้ว่านี่ไม่ได้ตอบคำถามอย่างแน่นอนแต่เป็นคำตอบที่ดีสำหรับคำถามที่คล้ายกันมาก)


1
ใช่มันง่ายต่อการพิสูจน์ [ ] ที่ใช้งานได้เมื่อมีจำนวนเต็มหนึ่งตัวที่ขาดหายไป แต่บ่อยครั้งที่มันล้มเหลวหากมีมากกว่าหนึ่งตัวที่หายไป ตัวอย่างเช่น0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7คือ 0 [การเขียน 2 x สำหรับกำลัง 2 ถึง x'th และ a ^ b สำหรับ xor b, xor ของ k ทั้งหมด <2 x เป็นศูนย์ - k ^ ~ k = (2 ^ x) - 1 สำหรับ k <2 ^ (x-1) และ k ^ ~ k ^ j ^ ~ j = 0 เมื่อ j = k + 2 ** (x-2) - ดังนั้น xor ของทั้งหมด แต่จำนวนหนึ่งคือค่า ของคนที่หายไป]
James Waldby - jwpat7

2
เมื่อฉันพูดถึงความคิดเห็นในคำตอบของ ircmaxell: ปัญหาไม่ได้บอกว่า "หมายเลขหนึ่งหายไป" มันบอกว่าจะหาหมายเลขที่ไม่รวมอยู่ในไฟล์ 4 พันล้านหมายเลขในไฟล์ หากเราสมมติว่าเป็นจำนวนเต็ม 32 บิตตัวเลขประมาณ 300 ล้านอาจหายไปจากไฟล์ โอกาสของ xor ของตัวเลขที่ตรงกับตัวเลขที่หายไปนั้นมีเพียงประมาณ 7%
James Waldby - jwpat7

นี่คือคำตอบที่ฉันคิดตอนแรกที่ฉันอ่านคำถาม แต่ในการตรวจสอบอย่างใกล้ชิดฉันคิดว่าคำถามนั้นคลุมเครือมากกว่านี้ FYI นี่เป็นคำถามที่ฉันคิดถึง: stackoverflow.com/questions/35185/…
Lee Netherton

18

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


4
ด้วยค่าที่เป็นไปได้มากกว่า 90% ของค่าที่เป็นไปได้ชุดตัวกรองบลูมของคุณอาจจะต้องเสื่อมโทรมเป็นบิตฟิลด์เพื่อให้คำตอบจำนวนมากใช้ไปแล้ว มิฉะนั้นคุณจะจบลงด้วย bitstring ที่เติมเต็มอย่างไร้ประโยชน์
Christopher Creutzig

@ คริสโตเฟอร์ความเข้าใจของฉันเกี่ยวกับตัวกรอง Bloom คือคุณไม่ได้รับ bitarray ที่เต็มไปจนกว่าคุณจะถึง 100%
พอล

... มิฉะนั้นคุณจะได้รับผลลบที่ผิดพลาด
พอล

@Paul อาร์เรย์บิตที่เติมเต็มจะให้ผลบวกเท็จซึ่งได้รับอนุญาต ในกรณีนี้ฟิลเตอร์บลูมจะมีแนวโน้มเสื่อมโทรมลงในกรณีที่วิธีการแก้ปัญหาซึ่งเป็นค่าลบจะส่งกลับค่าบวกปลอม
ataylor

1
@Paul: คุณสามารถรับ bitarray ที่กรอกได้ทันทีที่จำนวนฟังก์ชันแฮชคูณด้วยจำนวนรายการมีขนาดใหญ่เท่ากับความยาวของฟิลด์ แน่นอนว่าเป็นกรณีพิเศษ แต่ความน่าจะเป็นจะเพิ่มขึ้นอย่างรวดเร็ว
คริสโตเฟอร์ Creutzig

17

ขึ้นอยู่กับถ้อยคำปัจจุบันในคำถามเดิมทางออกที่ง่ายที่สุดคือ:

ค้นหาค่าสูงสุดในไฟล์แล้วเพิ่ม 1 ลงไป


5
จะเกิดอะไรขึ้นถ้า MAXINT รวมอยู่ในไฟล์
Petr Peller

@Petr Peller: ไลบรารี BIGINT จะลบข้อ จำกัด เกี่ยวกับขนาดจำนวนเต็มเป็นหลัก
oosterwal

2
@oosterwal ถ้าคำตอบนี้ได้รับอนุญาตกว่าที่คุณไม่จำเป็นต้องอ่านไฟล์ - เพียงแค่พิมพ์เป็นจำนวนมากเท่าที่จะทำได้
Nakilon

1
@oosterwal ถ้าจำนวนสุ่มของคุณใหญ่ที่สุดที่คุณสามารถพิมพ์ได้และมันอยู่ในไฟล์งานนี้จะไม่สามารถแก้ไขได้
Nakilon

3
@Nakilon: +1 คะแนนของคุณถูกยึด มันเทียบเท่ากับการหาจำนวนหลักทั้งหมดในไฟล์และพิมพ์ตัวเลขด้วยตัวเลขจำนวนมาก
oosterwal

14

ใช้ BitSetการใช้งานจำนวนเต็ม 4 พันล้าน (สมมติว่าเป็นจำนวนเต็ม 2 ^ 32) ลงใน BitSet ที่ 8 ต่อไบต์คือ 2 ^ 32/2 ^ 3 = 2 ^ 29 = ประมาณ 0.5 Gb

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

จริง ๆ แล้ว BitSet.nextClearBit (0) จะบอกคุณบิตแรกที่ไม่ได้ตั้งค่า

เมื่อดูที่ BitSet API ดูเหมือนว่ารองรับเพียง 0..MAX_INT ดังนั้นคุณอาจต้องใช้ 2 BitSets หนึ่งชุดสำหรับหมายเลข + และหนึ่งหมายเลขสำหรับรับ แต่ความต้องการหน่วยความจำไม่เปลี่ยนแปลง


1
หรือถ้าคุณไม่ต้องการใช้BitSet... ลองใช้ชุดของบิต ทำสิ่งเดียวกัน;)
jcolebrand

12

หากไม่มีการ จำกัด ขนาดวิธีที่เร็วที่สุดคือการใช้ความยาวของไฟล์และสร้างความยาวของไฟล์ + ตัวเลขสุ่ม 1 จำนวน (หรือเพียงแค่ "11111 ... " s) ข้อได้เปรียบ: คุณไม่จำเป็นต้องอ่านไฟล์และคุณสามารถลดการใช้หน่วยความจำให้เกือบเป็นศูนย์ได้ ข้อเสีย: คุณจะพิมพ์ตัวเลขเป็นพันล้าน

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


11

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

ทฤษฎี

ด้วยจำนวนเต็มใด ๆ ที่เป็น 0 ซึ่งมี2^nองค์ประกอบที่มีองค์ประกอบหนึ่งหายไปคุณสามารถค้นหาองค์ประกอบที่หายไปโดยเพียงแค่ xor-ing ค่าที่รู้จักกันเข้าด้วยกันเพื่อให้ได้จำนวนที่หายไป

หลักฐาน

ลองดูที่ n = 2 สำหรับ n = 2 เราสามารถแทนจำนวนเต็มที่ไม่ซ้ำกัน 4: 0, 1, 2, 3 พวกเขามีรูปแบบบิตของ:

  • 0 - 00
  • 1 - 01
  • 2 - 10
  • 3 - 11

ทีนี้ถ้าเราดูแต่ละบิตจะถูกตั้งเป็นสองเท่า ดังนั้นเนื่องจากมีการตั้งค่าเป็นจำนวนครั้งและแบบเอกสิทธิ์เฉพาะบุคคลหรือของตัวเลขจะให้ผลเป็น 0 ถ้าหมายเลขเดียวหายไปแบบเอกสิทธิ์เฉพาะบุคคลหรือจะให้ผลจำนวนที่เมื่อเอกสิทธิ์ ored ด้วยหมายเลขที่ขาดหายไปจะทำให้ 0 ดังนั้นหมายเลขที่ขาดหายไปและหมายเลขพิเศษที่ได้รับนั้นจะเหมือนกันทุกประการ หากเราลบ 2 ผลที่ได้คือ xor10 (หรือ 2)

ทีนี้ลองดู n + 1 ขอเรียกจำนวนครั้งในแต่ละบิตตั้งอยู่ในn, และจำนวนครั้งในแต่ละบิตตั้งอยู่ในx n+1 yค่าของyจะเท่ากับy = x * 2เนื่องจากมีxองค์ประกอบที่มีการn+1ตั้งค่าบิตเป็น 0 และxองค์ประกอบที่มีการn+1ตั้งค่าบิตเป็น 1 และเนื่องจาก2xจะเสมอกันเสมอn+1จะมีการตั้งค่าแต่ละบิตเป็นจำนวนครั้ง

ดังนั้นตั้งแต่n=2ผลงานและผลงานวิธีการแฮคเกอร์จะทำงานทุกค่าของn+1n>=2

อัลกอริทึมสำหรับช่วง 0 ตาม

มันค่อนข้างง่าย มันใช้หน่วยความจำ 2 * n บิตดังนั้นสำหรับช่วง <= 32, 2 32 บิตจำนวนเต็มจะทำงาน (ละเว้นหน่วยความจำใด ๆ ที่ใช้โดย file descriptor) และมันทำให้ไฟล์เดียวผ่าน

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

อัลกอริทึมสำหรับช่วงตามที่กำหนด

อัลกอริทึมนี้จะทำงานสำหรับช่วงของหมายเลขเริ่มต้นใด ๆ กับจำนวนสิ้นสุดใด ๆ ตราบใดที่ช่วงรวมเท่ากับ 2 ^ n ... นี่คือพื้นฐานอีกครั้งช่วงที่จะมีขั้นต่ำที่ 0 แต่มันต้องผ่าน 2 ผ่านไฟล์ (อันแรกที่จะคว้าค่าต่ำสุด, วินาทีเพื่อคำนวณ int ที่หายไป)

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

ช่วงโดยพลการ

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

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

โดยทั่วไปให้อ้างอิงช่วงประมาณ 0 อีกครั้งจากนั้นจะนับจำนวนค่าที่ไม่เรียงลำดับเพื่อผนวกเข้ากับการคำนวณแบบเอกสิทธิ์เฉพาะบุคคลหรือ จากนั้นจะเพิ่ม 1 ถึงจำนวนของค่าที่ไม่เรียงลำดับเพื่อดูแลค่าที่หายไป (นับหนึ่งค่าที่หายไป) จากนั้นให้ xoring ค่า n เพิ่มขึ้นทีละ 1 ครั้งจนกว่า n คือกำลังของ 2 ผลลัพธ์จะถูกนำกลับไปใช้ฐานเดิมอีกครั้ง เสร็จสิ้น

นี่คืออัลกอริทึมที่ฉันทดสอบใน PHP (ใช้อาร์เรย์แทนไฟล์ แต่มีแนวคิดเดียวกัน):

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

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

แนวทางอื่น

เนื่องจากเราสามารถใช้การจัดเรียงภายนอกทำไมไม่เพียงตรวจสอบช่องว่าง หากเราถือว่าไฟล์ถูกเรียงลำดับก่อนการรันอัลกอริทึมนี้:

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;

3
ปัญหาไม่ได้บอกว่า "หมายเลขหนึ่งหายไป" มันบอกว่าจะหาตัวเลขที่ไม่รวมอยู่ใน 4 พันล้านหมายเลขในไฟล์ หากเราสมมติว่าเป็นจำนวนเต็ม 32 บิตตัวเลขประมาณ 300 ล้านอาจหายไปจากไฟล์ โอกาสของ xor ของตัวเลขที่ตรงกับตัวเลขที่หายไปนั้นมีเพียงประมาณ 7%
James Waldby - jwpat7

หากคุณมีช่วงที่ต่อเนื่องกัน แต่ขาดหายไปที่ไม่ใช่ศูนย์ให้เพิ่มแทน xor sum(0..n) = n*(n+1)/2. missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[])ดังนั้น (แนวคิดรวบยอดจากคำตอบของ @ hammar)
Peter Cordes

9

คำถามเคล็ดลับเว้นแต่จะได้รับการยกมาอย่างไม่เหมาะสม แค่อ่านผ่านไฟล์ครั้งเดียวที่จะได้รับจำนวนเต็มสูงสุดและผลตอบแทนnn+1

แน่นอนคุณต้องมีแผนสำรองในกรณีที่n+1ทำให้เกิดการล้นจำนวนเต็ม


3
นี่คือโซลูชันที่ใช้งานได้ ... ยกเว้นเมื่อไม่เป็นเช่นนั้น ที่เป็นประโยชน์! :-)
dty

หากไม่มีการอ้างถึงอย่างไม่ถูกต้องคำถามจะไม่ถูก จำกัด ประเภทของจำนวนเต็มหรือแม้แต่กับภาษาที่ใช้ ภาษาสมัยใหม่จำนวนมากมีจำนวนเต็มเท่านั้นโดยหน่วยความจำที่มีอยู่ หากเลขจำนวนเต็มที่ใหญ่ที่สุดในไฟล์คือ> 10MB ขอให้โชคดีมากถ้าเป็นไปไม่ได้สำหรับงานที่สอง ทางออกที่ฉันชอบ
Jürgen Strobel

9

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

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

ควรพิมพ์10 bitcount - 1ซึ่งจะมากกว่า2 bitcountเสมอ ในทางเทคนิคจำนวนที่คุณต้องเอาชนะคือ2 bitcount - (4 * 10 9 - 1)เนื่องจากคุณรู้ว่ามี (4 พันล้าน -1) จำนวนเต็มอื่น ๆ ในไฟล์และแม้จะมีการบีบอัดข้อมูลที่สมบูรณ์แบบ หนึ่งบิตแต่ละ


ทำไมไม่เพียง แต่Console.Write( 1 << bitcount )แทนการวนซ้ำ? หากมีnบิตในไฟล์ดังนั้นหมายเลขบิต (_n_ + 1) ใด ๆ ที่มีหมายเลขนำหน้า 1 จะรับประกันได้ว่าจะใหญ่กว่าอย่างแน่นอน
Emmet

@Emmet - นั่นจะทำให้เกิดการล้นจำนวนเต็มเว้นแต่ไฟล์นั้นมีขนาดเล็กกว่าขนาดของ int (4 ไบต์ใน C #) C ++ อาจอนุญาตให้คุณใช้บางอย่างที่ใหญ่กว่า แต่ C # ดูเหมือนจะไม่อนุญาตอะไรนอกจาก 32-bit ints กับ<<โอเปอเรเตอร์ ไม่ว่าจะด้วยวิธีใดเว้นแต่คุณจะม้วนประเภทจำนวนเต็มมหึมาของคุณเองมันจะเป็นขนาดไฟล์ที่เล็กมาก การสาธิต: rextester.com/BLETJ59067
Justin Morgan

8
  • วิธีที่ง่ายที่สุดคือการหาจำนวนขั้นต่ำในไฟล์และส่งคืน 1 น้อยกว่านั้น สิ่งนี้ใช้ที่เก็บข้อมูล O (1) และเวลา O (n) สำหรับไฟล์ที่มีตัวเลข n อย่างไรก็ตามมันจะล้มเหลวถ้าช่วงของจำนวนถูก จำกัด ซึ่งอาจทำให้ min-1 ไม่ใช่ -a-number

  • วิธีการที่เรียบง่ายและตรงไปตรงมาของการใช้บิตแมปได้ถูกกล่าวถึงแล้ว วิธีนั้นใช้เวลา O (n) และการเก็บข้อมูล

  • นอกจากนี้ยังมีการกล่าวถึงวิธี 2-pass พร้อมการนับจำนวน 2 ^ 16 มันอ่านจำนวนเต็ม 2 * n ดังนั้นใช้เวลา O (n) และที่เก็บข้อมูล O (1) แต่ไม่สามารถจัดการชุดข้อมูลที่มีตัวเลขมากกว่า 2 ^ 16 อย่างไรก็ตามมันขยายได้อย่างง่ายดายไปยัง (เช่น) 2 ^ 60 จำนวนเต็ม 64- บิต 64 บิตโดยวิ่ง 4 รอบแทน 2 และปรับใช้หน่วยความจำขนาดเล็กได้อย่างง่ายดายโดยใช้ถังขยะจำนวนมากเท่าที่พอดีกับหน่วยความจำและเพิ่มจำนวนรอบ เวลารันไทม์ของเคสใดจะไม่ใช่ O (n) อีกต่อไป แต่จะเป็น O (n * log n)

  • วิธีการของ XOR'ing ตัวเลขทั้งหมดเข้าด้วยกันกล่าวถึงโดย rfrankel และที่ความยาวโดย ircmaxell ตอบคำถามที่ถามในstackoverflow # 35185 ตามที่ ltn100 ชี้ให้เห็น มันใช้ที่เก็บข้อมูล O (1) และเวลาทำงานของ O (n) หากในขณะนี้เราถือว่าจำนวนเต็ม 32 บิต XOR มีความน่าจะเป็น 7% ในการสร้างตัวเลขที่แตกต่าง เหตุผล: เนื่องจากตัวเลขที่แตกต่างกัน ~ 4G ที่ XOR จะรวมกันและสามารถ 300M ไม่ได้อยู่ในไฟล์จำนวนบิตที่ตั้งค่าในแต่ละตำแหน่งบิตมีโอกาสเท่ากันที่จะเป็นเลขคี่หรือคู่ ดังนั้นตัวเลข 2 ^ 32 จึงมีความเป็นไปได้ที่จะเกิดขึ้นอย่างเท่าเทียมกันเนื่องจากผลลัพธ์ XOR ซึ่ง 93% อยู่ในไฟล์แล้ว โปรดทราบว่าหากตัวเลขในไฟล์ไม่แตกต่างกันทั้งหมดความน่าจะเป็นของความสำเร็จของวิธี XOR จะเพิ่มขึ้น


7

ด้วยเหตุผลบางอย่างทันทีที่ฉันอ่านปัญหานี้ฉันก็นึกถึงเส้นทแยงมุม ฉันสมมติว่าเป็นจำนวนเต็มขนาดใหญ่โดยพลการ

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


+1 สำหรับความคิดสร้างสรรค์ (และเอาต์พุตเคสที่แย่ที่สุดที่เล็กที่สุดสำหรับโซลูชัน single-pass)
hmakholm ออกเดินทางจากโมนิกา

แต่มีไม่ถึง 4 พันล้านบิตที่จะตัดในแนวทแยงมีเพียง 32 คุณจะจบลงด้วยตัวเลข 32 บิตซึ่งแตกต่างจากตัวเลข 32 ตัวแรกในรายการ
Brian Gordon

@ Henning มันแทบไม่ผ่านเลย คุณยังต้องแปลงจากนารี่เป็นไบนารี แก้ไข: ฉันเดาว่ามันผ่านไปหนึ่งไฟล์ ไม่เป็นไร.
Brian Gordon

@Brian มีบางสิ่งที่ "unary" ที่นี่หรือไม่ คำตอบคือสร้างคำตอบแบบไบนารีครั้งละหนึ่งบิตและจะอ่านไฟล์อินพุตเพียงครั้งเดียวทำให้ผ่านครั้งเดียว (หากต้องการเอาท์พุททศนิยมสิ่งต่าง ๆ จะมีปัญหา - คุณควรสร้างเลขทศนิยมหนึ่งหลักต่อหนึ่งหมายเลขอินพุตสามตัวและยอมรับการเพิ่ม 10% ในบันทึกของหมายเลขเอาท์พุต)
hmakholm ออกเดินทางจากโมนิกา

2
@Henning ปัญหาไม่สมเหตุสมผลสำหรับจำนวนเต็มขนาดใหญ่โดยพลการเนื่องจากมีคนจำนวนมากชี้ให้เห็นว่ามันเป็นเรื่องไม่สำคัญที่จะหาจำนวนที่ใหญ่ที่สุดและเพิ่มหนึ่งหรือสร้างจำนวนที่ยาวมากจากไฟล์ของตัวเอง วิธีการแก้เส้นทแยงมุมนี้ไม่เหมาะสมอย่างยิ่งเพราะแทนที่จะแตกกิ่งที่iบิตคุณสามารถเอาท์พุท 1 บิต 4 พันล้านครั้งและโยนเพิ่มอีก 1 ในตอนท้าย ฉันตกลงกับการมีจำนวนเต็มขนาดใหญ่โดยพลการในอัลกอริทึมแต่ฉันคิดว่าปัญหาคือการส่งออกจำนวนเต็ม 32 บิตที่ขาดหายไป มันไม่สมเหตุสมผลเลย
Brian Gordon

6

คุณสามารถใช้ค่าสถานะบิตเพื่อทำเครื่องหมายว่ามีจำนวนเต็มหรือไม่

หลังจากสำรวจไฟล์ทั้งหมดแล้วให้สแกนแต่ละบิตเพื่อดูว่ามีจำนวนหรือไม่

สมมติว่าแต่ละจำนวนเต็มคือ 32 บิตพวกเขาจะพอดีใน RAM 1 GB หากทำการตั้งค่าสถานะบิต


0.5 Gb เว้นแต่ว่าคุณได้กำหนดไบต์ใหม่เป็น 4 bits ;-)
dty

2
@dty ฉันคิดว่าเขาหมายถึง "สบาย" เพราะในนั้นจะมีพื้นที่มากมายใน 1Gb
corsiKa

6

ตัดพื้นที่สีขาวและอักขระที่ไม่ใช่ตัวเลขออกจากไฟล์และต่อท้าย 1. ไฟล์ของคุณมีหมายเลขเดียวที่ไม่อยู่ในไฟล์ต้นฉบับ

จาก Reddit โดย Carbonetc


รักมัน! แม้ว่ามันจะไม่ใช่คำตอบที่เขาต้องการ ... : D
Johann du Toit

6

นี่เป็นอีกวิธีการแก้ปัญหาที่ง่ายมากซึ่งส่วนใหญ่ใช้เวลานานในการรัน แต่ใช้หน่วยความจำน้อยมาก

ปล่อยให้จำนวนเต็มที่เป็นไปได้ทั้งหมดอยู่ในช่วงจากint_minถึงint_maxและ bool isNotInFile(integer)ฟังก์ชันที่คืนค่าเป็นจริงหากไฟล์ไม่มีจำนวนเต็มและ false อย่างอื่น (โดยการเปรียบเทียบจำนวนเต็มนั้นกับจำนวนเต็มแต่ละตัวในไฟล์)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

คำถามนั้นเกี่ยวกับอัลกอริทึมสำหรับisNotInFileฟังก์ชั่น โปรดตรวจสอบให้แน่ใจว่าคุณเข้าใจคำถามก่อนตอบ
Aleks G

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

ฉันคิดว่านี่เป็นคำตอบที่ถูกกฎหมาย ยกเว้น I / O คุณต้องการเพียงหนึ่งจำนวนเต็มและธงบูล
Brian Gordon

@Aleks G - ฉันไม่เห็นสาเหตุที่ทำเครื่องหมายว่าผิด เราทุกคนยอมรับว่ามันเป็นอัลกอริทึมที่ช้าที่สุดของทั้งหมด :-) แต่มันใช้งานได้และต้องการเพียง 4 ไบต์เพื่ออ่านไฟล์ คำถามเดิมไม่ได้กำหนดให้ไฟล์สามารถอ่านได้เพียงครั้งเดียว
Simon Mourier

1
@Aleks G - ถูกต้อง ฉันไม่เคยพูดว่าคุณพูดอย่างนั้น เราเพิ่งบอกว่า IsNotInFile สามารถนำไปใช้งานได้เล็กน้อยโดยใช้ลูปในไฟล์: เปิดในขณะที่ไม่ใช่ Eof {Read Integer; Return False ถ้า Integer = i; Else Continue;} ต้องการหน่วยความจำเพียง 4 ไบต์
Simon Mourier

5

สำหรับข้อ จำกัด หน่วยความจำ 10 MB:

  1. แปลงตัวเลขเป็นการแทนแบบไบนารี่
  2. สร้างต้นไม้ไบนารีที่ left = 0 และ right = 1
  3. แทรกแต่ละหมายเลขในต้นไม้โดยใช้การแทนเลขฐานสอง
  4. ถ้าใส่หมายเลขแล้วใบไม้จะถูกสร้างขึ้นแล้ว

เมื่อเสร็จแล้วเพียงนำเส้นทางที่ไม่ได้สร้างมาก่อนเพื่อสร้างหมายเลขที่ร้องขอ

4 พันล้านหมายเลข = 2 ^ 32 หมายถึง 10 MB อาจไม่เพียงพอ

แก้ไข

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

แก้ไขครั้งที่สอง

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


6
... และมันจะพอดีกับ 10 MB อย่างไร
hmakholm ออกเดินทางจากโมนิกา

วิธีการ: จำกัด ความลึกของ BTree กับสิ่งที่จะพอดีกับ 10MB; นี่หมายความว่าคุณจะได้ผลลัพธ์เป็นชุด {false positive | บวก} และคุณสามารถทำซ้ำผ่านสิ่งนั้นและใช้เทคนิคอื่น ๆ เพื่อค้นหาค่า
Jonathan Dickinson

5

ฉันจะตอบรุ่น 1 GB:

มีข้อมูลไม่เพียงพอในคำถามดังนั้นฉันจะระบุสมมติฐานบางอย่างก่อน:

จำนวนเต็มคือ 32 บิตด้วยช่วง -2,147,483,648 ถึง 2,147,483,647

หลอกรหัส:

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}

4

ตราบใดที่เราทำคำตอบที่สร้างสรรค์นี่ก็เป็นอีกคำตอบหนึ่ง

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


3

การกำจัดบิต

วิธีหนึ่งคือการกำจัดบิตอย่างไรก็ตามสิ่งนี้อาจไม่ได้ผลลัพธ์จริง ๆ (โอกาสที่มันจะไม่เกิดขึ้น) Psuedocode:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

นับจำนวนบิต

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

ลอจิกช่วง

ติดตามรายการที่เรียงลำดับช่วง (เรียงลำดับตามการเริ่มต้น) ช่วงถูกกำหนดโดยโครงสร้าง:

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

ผ่านแต่ละค่าในไฟล์และลองและลบออกจากช่วงปัจจุบัน วิธีนี้ไม่มีการรับประกันหน่วยความจำ แต่ควรทำค่อนข้างดี


3

2 128 * 10 18 + 1 (ซึ่งคือ (2 8 ) 16 * 10 18 + 1) - วันนี้มันจะเป็นคำตอบทั่วไปไม่ได้เหรอ? นี่แสดงถึงจำนวนที่ไม่สามารถเก็บไว้ในไฟล์ 16 EB ซึ่งเป็นขนาดไฟล์สูงสุดในระบบไฟล์ปัจจุบัน


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

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

3

ฉันคิดว่านี่เป็นปัญหาที่แก้ไขแล้ว (ดูด้านบน) แต่มีกรณีที่น่าสนใจที่ควรทราบเพราะอาจถูกถาม:

หากมีจำนวนเต็ม 4,294,967,295 (2 ^ 32 - 1) 32- บิตโดยไม่มีการทำซ้ำดังนั้นจึงหายไปเพียงหนึ่งเดียวนั่นคือวิธีแก้ปัญหาอย่างง่าย

เริ่มต้นผลรวมสะสมที่ศูนย์และสำหรับแต่ละจำนวนเต็มในไฟล์ให้เพิ่มจำนวนเต็มนั้นด้วยโอเวอร์โฟลว์ 32- บิต (อย่างมีประสิทธิภาพ, runningTotal = (runningTotal + nextInteger)% 4294967296) เมื่อเสร็จแล้วให้เพิ่ม 4294967296/2 ในผลรวมการรันอีกครั้งด้วยการโอเวอร์โฟลว์แบบ 32 บิต ลบออกจาก 4294967296 และผลลัพธ์คือจำนวนเต็มที่ขาดหายไป

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

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

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}

@Nakilon และ TheDayTurns ได้ชี้ให้เห็นในความคิดเห็นต่อคำถามเดิม
ไบรอันกอร์ดอน

3

อย่างที่ Ryan บอกไว้โดยพื้นฐานให้เรียงไฟล์แล้วข้ามจำนวนเต็มและเมื่อค่าถูกข้ามไปคุณจะได้ :)

แก้ไขที่ downvoters: OP กล่าวถึงว่าไฟล์สามารถเรียงลำดับได้ดังนั้นนี่จึงเป็นวิธีการที่ถูกต้อง


ส่วนที่สำคัญอย่างหนึ่งคือคุณควรจะทำในขณะที่คุณไปคุณจะต้องอ่านเพียงครั้งเดียว การเข้าถึงหน่วยความจำกายภาพช้า
Ryan Amos

@ryan เรียงลำดับภายนอกในกรณีส่วนใหญ่ผสานเรียงลำดับดังนั้นในการผสานสุดท้ายที่คุณสามารถทำกา :)
วงล้อประหลาด

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

คุณจะเรียงลำดับจำนวนเต็ม 4 พันล้านตัวได้อย่างไรเมื่อคุณมีหน่วยความจำเพียง 1 GB หากคุณใช้หน่วยความจำแบบ virtyual จะใช้เวลา loooong เมื่อบล็อกหน่วยความจำได้รับการเพจเข้าและออกจากหน่วยความจำกายภาพ
Klas Lindbäck

4
@klas merge sortถูกออกแบบมาเพื่อสิ่งนั้น
ratchet freak

2

หากคุณไม่ยอมรับข้อ จำกัด แบบ 32 บิตเพียงส่งคืนหมายเลข 64 บิตที่สร้างแบบสุ่ม (หรือ 128 บิตหากคุณเป็นคนมองในแง่ร้าย) โอกาสของการชนคือ1 in 2^64/(4*10^9) = 4611686018.4(ประมาณ 1 ใน 4 พันล้าน) คุณจะถูกต้องเกือบตลอดเวลา!

(ล้อเล่น ... เป็นแบบ)


ฉันเห็นสิ่งนี้ได้รับการแนะนำแล้ว :) upvotes สำหรับคนเหล่านั้น
Peter Gibson

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

@PeterCordes หมายเลข 128 บิตที่สร้างแบบสุ่มนั้นแม่นยำว่า UUID ทำงานอย่างไร - พวกเขายังกล่าวถึงวันเกิดที่ขัดแย้งกันเมื่อคำนวณความน่าจะเป็นของการชนในหน้า Wikipedia UUID
Peter Gibson

ตัวแปร: ค้นหาค่าสูงสุดในชุดเพิ่ม 1
ฟิล

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