คำถามสัมภาษณ์ง่ายขึ้นยากขึ้น: ตัวเลขที่ระบุ 1..100, ค้นหาหมายเลขที่หายไปที่ระบุว่า k หายไปอย่างแน่นอน


1146

ฉันมีประสบการณ์การสัมภาษณ์งานที่น่าสนใจสักพัก คำถามเริ่มง่ายจริงๆ:

ไตรมาสที่ 1 : เรามีถุงที่มีตัวเลข1, 2, 3, ... 100, แต่ละหมายเลขปรากฏขึ้นหนึ่งครั้งดังนั้นจึงมี 100 หมายเลข ตอนนี้จะมีการสุ่มเลือกหมายเลขหนึ่งออกจากกระเป๋า ค้นหาหมายเลขที่หายไป

ฉันเคยได้ยินคำถามสัมภาษณ์นี้มาก่อนแน่นอนดังนั้นฉันจึงตอบอย่างรวดเร็วไปตาม:

A1 : ผลรวมของตัวเลข1 + 2 + 3 + … + Nคือ(N+1)(N/2)(ดูWikipedia: ผลรวมของชุดเลขคณิต ) สำหรับผลรวมคือN = 1005050

5050ดังนั้นหากตัวเลขทั้งหมดที่มีอยู่ในถุงรวมจะตรง เนื่องจากหมายเลขหนึ่งหายไปผลรวมจะน้อยกว่านี้และความแตกต่างคือจำนวนนั้น ดังนั้นเราสามารถหาจำนวนที่ขาดหายไปในO(N)เวลาและO(1)สถานที่

เมื่อมาถึงจุดนี้ฉันคิดว่าฉันทำได้ดี แต่จู่ ๆ คำถามก็ถึงจุดเปลี่ยนที่ไม่คาดคิด:

Q2 : ถูกต้อง แต่ตอนนี้คุณจะทำอย่างไรถ้าหมายเลขสองหายไป?

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

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

คำตอบของผู้สัมภาษณ์ทำให้ฉันประหลาดใจ: คุณสามารถสรุปเทคนิคเพื่อหาตัวเลขที่หายไป 3 ตัว ในความเป็นจริงคุณสามารถพูดคุยทั่วไปเพื่อหาตัวเลขที่หายไปk

Qk : หากตัวเลขkหายไปจากกระเป๋าคุณจะค้นหาได้อย่างมีประสิทธิภาพได้อย่างไร

เมื่อไม่กี่เดือนที่ผ่านมาและฉันก็ยังนึกไม่ออกว่าเทคนิคนี้คืออะไร เห็นได้ชัดว่ามีΩ(N)เวลาผูกพันลดลงเนื่องจากเราต้องสแกนตัวเลขทั้งหมดอย่างน้อยหนึ่งครั้ง แต่สัมภาษณ์ยืนยันว่าเวลาและพื้นที่ซับซ้อนของเทคนิคการแก้ (ลบO(N)สแกนอินพุตเวลา) ถูกกำหนดไว้ในkไม่N

ดังนั้นคำถามที่นี่ง่าย ๆ :

  • คุณจะแก้ปัญหาQ2อย่างไร
  • คุณจะแก้ปัญหาQ3 ได้อย่างไร?
  • คุณจะแก้ปัญหาQkอย่างไร

ชี้แจง

  • โดยทั่วไปมีตัวเลขNตั้งแต่ 1 .. Nไม่ใช่เพียง 1..100
  • ฉันไม่ได้กำลังมองหาวิธีการแก้ปัญหา set-based ที่ชัดเจนเช่นการใช้ชุดบิตการเข้ารหัสการมี / ไม่มีแต่ละหมายเลขด้วยค่าของบิตที่กำหนดดังนั้นการใช้O(N)บิตในพื้นที่เพิ่มเติม เราไม่สามารถจัดหาพื้นที่เพิ่มเติมให้กับNได้
  • ฉันยังไม่ได้มองหาวิธีการเรียงลำดับแรกที่ชัดเจน วิธีนี้และวิธีการตั้งค่าเป็นสิ่งที่ควรพูดถึงในการสัมภาษณ์ (เป็นเรื่องง่ายที่จะนำไปปฏิบัติและขึ้นอยู่กับNสามารถนำไปใช้ได้จริง) ฉันกำลังมองหาวิธีการแก้ปัญหาจอกศักดิ์สิทธิ์ (ซึ่งอาจหรืออาจจะไม่สามารถนำไปใช้ได้จริง แต่มีลักษณะเชิงซีโมติกที่ต้องการ)

ดังนั้นอีกครั้งแน่นอนคุณต้องสแกนอินพุตO(N)แต่คุณสามารถจับข้อมูลเพียงเล็กน้อย (กำหนดในรูปของkไม่ใช่N ) จากนั้นต้องค้นหาkตัวเลขที่หายไปอย่างใด


7
@polygenel น้ำมันหล่อลื่นขอบคุณสำหรับคำชี้แจง "ฉันกำลังมองหาอัลกอริทึมที่ใช้ O (n) เวลาและ O (K) พื้นที่ที่ K มีการนับตัวเลขขาด" จะได้รับความชัดเจนจากจุดเริ่มต้นใน ;-)
เดฟทุม

7
คุณควรแม่นยำในคำสั่ง Q1 ที่คุณไม่สามารถเข้าถึงหมายเลขตามลำดับ สิ่งนี้อาจดูเหมือนชัดเจนสำหรับคุณ แต่ฉันไม่เคยได้ยินคำถามและคำว่า "กระเป๋า" (ซึ่งหมายถึง "มัลติเซ็ต" ด้วย) ทำให้เกิดความสับสน
Jérémie

7
โปรดอ่านต่อไปนี้เป็นคำตอบที่ให้ไว้ที่นี่ไร้สาระ: stackoverflow.com/questions/4406110/…

18
วิธีการหาผลรวมของตัวเลขต้องใช้พื้นที่ log (N) นอกเสียจากคุณจะพิจารณาความต้องการพื้นที่สำหรับจำนวนเต็มที่ไม่ จำกัด ให้เป็น O (1) แต่ถ้าคุณอนุญาตสำหรับจำนวนเต็มที่ไม่ได้ จำกัด คุณจะมีพื้นที่มากเท่าที่คุณต้องการด้วยจำนวนเต็มเพียงตัวเดียว
Udo Klein

3
โดยวิธีการแก้ปัญหาทางเลือกที่ดีงามสำหรับ Q1 สามารถคำนวณXORตัวเลขทั้งหมดจาก1ถึงnแล้วผล xoring กับตัวเลขทั้งหมดในอาร์เรย์ที่กำหนด ในท้ายที่สุดคุณมีหมายเลขที่หายไป ในการแก้ปัญหานี้คุณไม่จำเป็นต้องกังวลเกี่ยวกับการล้นเช่นเดียวกับการสรุป
sbeliakov

คำตอบ:


590

นี่คือบทสรุปของDimitris Andreou ของ การเชื่อมโยง

จำผลรวมของพลัง i-th โดยที่ i = 1,2, .. , k สิ่งนี้จะช่วยลดปัญหาในการแก้ระบบสมการ

a 1 + a 2 + ... + a k = b 1

a 1 2 + a 2 2 + ... + a k 2 = b 2

...

a 1 k + a 2 k + ... + a k k = b k

ใช้ข้อมูลประจำตัวของนิวตันรู้ว่าฉันอนุญาตให้คำนวณได้

c 1 = a 1 + a 2 + ... a k

c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k

...

c k = a 1 a 2 ... a k

ถ้าคุณขยายพหุนาม (XA 1 ) ... (XA k ) ค่าสัมประสิทธิ์จะตรงค1 , ... , คk - ดูสูตรVièteของ เนื่องจากปัจจัยพหุนามทุกอย่างไม่ซ้ำกัน (วงแหวนของชื่อพหุนามเป็นโดเมนแบบยุคลิด ) นี่หมายความว่าฉันมีความมุ่งมั่นที่ไม่ซ้ำกันถึงการเปลี่ยนแปลง

นี่เป็นการพิสูจน์ว่าพลังในการจดจำนั้นเพียงพอที่จะกู้คืนตัวเลขได้ สำหรับค่าคงที่ k นี่เป็นวิธีที่ดี

อย่างไรก็ตามเมื่อ k มีการเปลี่ยนแปลงวิธีการโดยตรงในการคำนวณ c 1 , ... , c kนั้นมีราคาแพงอย่างไม่น่าเชื่อเนื่องจาก c kเป็นผลิตภัณฑ์ของตัวเลขที่หายไปทั้งหมดขนาด n! / (nk)! ที่จะเอาชนะนี้ดำเนินการคำนวณใน Z Qฟิลด์ที่ Q เป็นสำคัญเช่นว่า n <= Q <2n - มันมีอยู่โดยสัจพจน์ของเบอร์แทรนด์ หลักฐานไม่จำเป็นต้องเปลี่ยนเนื่องจากสูตรยังคงอยู่และการแยกตัวประกอบของพหุนามยังคงไม่เหมือนใคร นอกจากนี้คุณยังต้องอัลกอริทึมสำหรับตัวประกอบเหนือฟิลด์ จำกัด เป็นตัวอย่างหนึ่งโดยBerlekampหรือต้นเสียง-Zassenhaus

pseudocode ระดับสูงสำหรับค่าคงที่ k:

  • คำนวณพลังที่ i ของตัวเลขที่กำหนด
  • ลบออกเพื่อให้ได้จำนวนผลบวกของจำนวนที่ไม่รู้จัก โทรเงินก้อนขฉัน
  • ใช้อัตลักษณ์ของนิวตันเพื่อคำนวณค่าสัมประสิทธิ์จาก b i ; เรียกพวกเขาคฉัน โดยทั่วไป c 1 = b 1 ; c 2 = (c 1 b 1 - b 2 ) / 2; ดู Wikipedia สำหรับสูตรที่แน่นอน
  • ปัจจัยพหุนาม x k -c 1 x k-1 + ... + C k
  • รากของพหุนามที่มีตัวเลขที่จำเป็นต้องมี1 ... เป็นk

สำหรับ k ที่แตกต่างกันให้หานายก n <= q <2n โดยใช้เช่น Miller-Rabin และทำตามขั้นตอนโดยลดจำนวนโมดูโล q ทั้งหมด

แก้ไข: เวอร์ชันก่อนหน้าของคำตอบนี้ระบุว่าแทนที่จะเป็น Z qโดยที่ q เป็นไพรม์คุณสามารถใช้ฟิลด์ จำกัด ของคุณลักษณะ 2 (q = 2 ^ (log n)) นี่ไม่ใช่กรณีเนื่องจากสูตรของนิวตันต้องการการหารด้วยตัวเลขถึง k


6
q = 2^(log n)คุณไม่จำเป็นต้องใช้ข้อมูลที่สำคัญคุณยังสามารถใช้ (คุณสร้าง super- และห้อยได้อย่างไร!)
Heinrich Apfelmus

49
+1 นี่ฉลาดจริงๆ ในขณะเดียวกันก็เป็นที่น่าสงสัยไม่ว่าจะคุ้มค่ากับความพยายามจริง ๆ หรือไม่ (ส่วนหนึ่งของ) การแก้ปัญหานี้กับปัญหาที่เกิดขึ้นจริงสามารถนำกลับมาใช้ใหม่ได้อีกทางหนึ่ง และแม้ว่านี่จะเป็นปัญหาในโลกแห่งความเป็นจริง แต่ในหลาย ๆ แพลตฟอร์มการO(N^2)แก้ปัญหาที่ไม่สำคัญที่สุดอาจจะทำได้ดีกว่าความงามนี้ด้วยซ้ำNไป ทำให้ฉันคิดถึงสิ่งนี้: tinyurl.com/c8fwgwอย่างไรก็ตามงานที่ยอดเยี่ยม! ฉันจะไม่อดทนเลยที่จะคลานผ่านคณิตศาสตร์ทั้งหมด :)
back2dos

167
ฉันคิดว่านี่เป็นคำตอบที่ยอดเยี่ยม ฉันคิดว่าสิ่งนี้ยังแสดงให้เห็นว่าคำถามสัมภาษณ์ที่น่าสงสารมันจะขยายจำนวนที่ขาดไปมากกว่าหนึ่งคำถามได้อย่างไร แม้แต่คนแรกก็เป็น gotchya แต่ก็เป็นเรื่องธรรมดาพอที่มันจะแสดงว่า "คุณได้เตรียมการสัมภาษณ์" แต่การคาดหวังว่า CS ที่สำคัญต้องรู้เกินกว่า k = 1 (โดยเฉพาะ "ในจุดที่" ในการสัมภาษณ์) ค่อนข้างโง่
corsiKa

5
สิ่งนี้กำลังทำการเข้ารหัส Reed โซโลมอนบนอินพุตอย่างมีประสิทธิภาพ
David Ehrmann

78
ฉันเดิมพันการป้อนหมายเลขทั้งหมดใน a hash setและวนซ้ำใน1...Nชุดโดยใช้การค้นหาเพื่อตรวจสอบว่าตัวเลขหายไปจะเป็นวิธีทั่วไปที่เร็วที่สุดโดยเฉลี่ยเกี่ยวกับkรูปแบบที่หลากหลายวิธีแก้ปัญหาที่บำรุงรักษาและเข้าใจได้มากที่สุด แน่นอนว่าวิธีการทางคณิตศาสตร์นั้นน่าประทับใจ แต่ก็มีบางอย่างที่คุณต้องเป็นวิศวกรไม่ใช่นักคณิตศาสตร์ โดยเฉพาะอย่างยิ่งเมื่อธุรกิจมีส่วนเกี่ยวข้อง
v.oddou

243

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

ทีนี้ถ้ามีเพียงคนเท่านั้นที่จะเริ่มลบคำตอบที่ถูกบอกเล่าหรือถูกแทนที่โดยการรักษาของ Muthukrishnan และทำให้ข้อความนี้ง่ายต่อการค้นหา :)


ดูคำตอบที่เกี่ยวข้องโดยตรงกับsdcvvcซึ่งรวมถึง pseudocode (hurray! ไม่จำเป็นต้องอ่านสูตรทางคณิตศาสตร์ที่ยุ่งยาก :)) (ขอบคุณการทำงานที่ยอดเยี่ยม!)


โอ้ ... น่าสนใจ ฉันต้องยอมรับว่าคณิตศาสตร์ของฉันสับสนเล็กน้อย แต่ฉันก็กำลังอ่านอยู่ อาจเปิดทิ้งไว้เพื่อดูเพิ่มเติมในภายหลัง :) และ +1 เพื่อรับลิงค์นี้สามารถค้นหาได้มากขึ้น ;-)
Chris

2
ลิงก์ Google หนังสือไม่ทำงานสำหรับฉัน นี่คือรุ่นที่ดีกว่า [ไฟล์ PostScript]
Heinrich Apfelmus

9
ว้าว. ฉันไม่ได้คาดหวังว่าสิ่งนี้จะได้รับการสนับสนุน! ครั้งสุดท้ายที่ฉันโพสต์การอ้างอิงถึงการแก้ปัญหา (Knuth ในกรณีนั้น) แทนที่จะพยายามที่จะแก้ปัญหาด้วยตัวเองมันเป็น downvoted จริง ๆ : stackoverflow.com/questions/3060104/ …บรรณารักษ์ในตัวฉันชื่นชมยินดีขอบคุณ
Dimitris Andreou

@Apfelmus โปรดทราบว่านี่เป็นฉบับร่าง (ฉันไม่โทษคุณแน่นอนฉันสับสนร่างของจริงสำหรับเกือบปีก่อนหาหนังสือ) หากลิงก์ไม่ทำงานคุณสามารถไปที่books.google.co.thและค้นหา "อัลกอริทึม Muthukrishnan data stream" (โดยไม่ต้องใส่เครื่องหมายอัญประกาศ) ซึ่งเป็นรายการแรกที่ปรากฏขึ้น
Dimitris Andreou

2
โปรดอ่านต่อไปนี้เป็นคำตอบที่ให้ไว้ที่นี่ไร้สาระ: stackoverflow.com/questions/4406110/…

174

เราสามารถแก้ Q2 ได้โดยรวมทั้งตัวเลขเองและกำลังสองของตัวเลข

จากนั้นเราสามารถลดปัญหาให้

k1 + k2 = x
k1^2 + k2^2 = y

จำนวนxและyจำนวนเงินต่ำกว่าค่าที่คาดหวัง

การทดแทนช่วยให้เรา:

(x-k2)^2 + k2^2 = y

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


7
+1; ฉันลองสูตรใน Maple เพื่อดูตัวเลขที่เลือกและใช้งานได้ ฉันยังไม่สามารถโน้มน้าวใจตัวเองได้ว่าทำไมมันถึงได้ผล
polygenelubricants

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

5
ธรรมชาติของสมการหมายความว่าคุณจะได้ค่า k2 สองค่าจากสมการนั้น อย่างไรก็ตามจากสมการแรกที่คุณใช้ในการสร้าง k1 คุณจะเห็นได้ว่าค่า k2 สองค่านี้จะหมายความว่า k1 เป็นค่าอื่นดังนั้นคุณจึงมีวิธีแก้ปัญหาสองตัวที่มีตัวเลขเหมือนกันในทางตรงกันข้าม หากคุณประกาศว่า k1> k2 อย่างไม่ยุติธรรมคุณจะมีวิธีแก้ปัญหาสมการกำลังสองและวิธีแก้ปัญหาเดียวโดยรวม และโดยธรรมชาติของคำถามจะมีคำตอบอยู่เสมอดังนั้นจึงใช้ได้เสมอ
Chris

3
สำหรับผลรวม k1 + k2 ที่กำหนดมีหลายคู่ เราสามารถเขียนคู่เหล่านี้เป็น K1 = a + b และ K2 = ab โดยที่ a = (K1 + k2 / 2) เป็นเอกลักษณ์สำหรับผลรวมที่กำหนด ผลรวมของช่องสี่เหลี่ยม (a + b) ** 2 + (ab) ** 2 = 2 * (a 2 + b 2) สำหรับผลรวม K1 + K2 ที่กำหนด2 เทอมจะได้รับการแก้ไขและเราเห็นว่าผลรวมของสแควร์สจะไม่ซ้ำกันเนื่องจากเทอมb 2 ดังนั้นค่า x และ y จึงไม่ซ้ำกันสำหรับคู่ของจำนวนเต็ม
phkahler

8
นี่มันเจ๋งมาก. @ user3281743 นี่เป็นตัวอย่าง ปล่อยให้ตัวเลขที่หายไป (k1 และ k2) เท่ากับ 4 และ 6 รวม (1 -> 10) = 55 และผลรวม (1 ^ 2 -> 10 ^ 2) = 385 ตอนนี้ให้ x = 55 - (ผลรวม (จำนวนที่เหลือทั้งหมด )) และ y = 385 - (ผลรวม (กำลังสองของจำนวนที่เหลือทั้งหมด)) จึง x = 10 และ y = 52 การทดแทนตามที่แสดงซึ่งทำให้เรามี: (10 - k2) ^ 2 + k2 ^ 2 = 52 ซึ่งคุณสามารถ ลดความซับซ้อนไปที่: 2k ^ 2 - 20k + 48 = 0 การแก้สมการกำลังสองให้ 4 และ 6 เป็นคำตอบ
AlexKoren

137

ดังที่ @j_random_hacker ชี้ให้เห็นว่านี่ค่อนข้างคล้ายกับการค้นหารายการที่ซ้ำกันในเวลา O (n) และพื้นที่ O (1)และการปรับคำตอบของฉันก็ใช้ได้ที่นี่เช่นกัน

สมมติว่า "ถุง" แทนด้วยA[]ขนาดอาร์เรย์ที่ใช้ 1N - kเราสามารถแก้ปัญหา Qk ในO(N)เวลาและO(k)พื้นที่เพิ่มเติม

อันดับแรกเราขยายอาร์เรย์ของเราA[]ตามkองค์ประกอบเพื่อให้มีขนาดเท่าNนี้ นี่คือO(k)พื้นที่เพิ่มเติม จากนั้นเราเรียกใช้อัลกอริธึมหลอกรหัสต่อไปนี้:

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

ลูปแรกเริ่มต้นkรายการพิเศษให้เหมือนกับรายการแรกในอาเรย์ (นี่เป็นเพียงค่าที่เรารู้ว่ามีอยู่ในอาเรย์แล้ว - หลังจากขั้นตอนนี้รายการใด ๆ ที่หายไปในอาเรย์เริ่มต้นของขนาดN-kคือ ยังขาดหายไปในอาเรย์เสริม)

การวนรอบที่สองอนุญาตให้มีอาร์เรย์ที่ขยายเพิ่มดังนั้นหากองค์ประกอบนั้น xA[x]เป็นปัจจุบันอย่างน้อยหนึ่งครั้งจากนั้นหนึ่งของรายการเหล่านั้นจะอยู่ที่ตำแหน่ง

โปรดทราบว่าแม้ว่ามันจะมีลูปซ้อนกัน แต่ก็ยังทำงานได้O(N)ทันเวลา - การสลับจะเกิดขึ้นก็ต่อเมื่อมีสิ่งiนั้นA[i] != iและการสลับแต่ละครั้งจะตั้งค่าองค์ประกอบอย่างน้อยหนึ่งองค์ประกอบเช่นนั้นA[i] == iโดยที่ไม่เคยเป็นจริงมาก่อน ซึ่งหมายความว่าจำนวนรวมของการแลกเปลี่ยน (และจำนวนรวมของการดำเนินการของwhileร่างกายห่วง) N-1เป็นอย่างมาก

ลูปที่สามพิมพ์ดัชนีเหล่านั้นของอาร์เรย์iที่ไม่ได้ใช้งานโดยค่าi- ซึ่งหมายความว่าiต้องหายไป


4
ฉันสงสัยว่าทำไมคนไม่กี่คนโหวตคำตอบนี้และไม่ได้ทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้อง นี่คือรหัสใน Python มันทำงานในเวลา O (n) และต้องการพื้นที่พิเศษ O (k) pastebin.com/9jZqnTzV
wall-e

3
@caf นี้ค่อนข้างคล้ายกับการตั้งค่าบิตและนับสถานที่ที่บิตเป็น 0 และฉันคิดว่าเมื่อคุณสร้างอาร์เรย์จำนวนเต็มหน่วยความจำเพิ่มเติมจะถูกครอบครอง
Fox

5
"การตั้งค่าบิตและนับสถานที่ที่บิตเป็น 0" ต้องใช้พื้นที่พิเศษ O (n) โซลูชันนี้แสดงวิธีใช้พื้นที่พิเศษ O (k)
caf

7
ไม่ทำงานกับสตรีมเป็นอินพุตและแก้ไขอาเรย์ใส่ข้อมูล (แม้ว่าฉันจะชอบมันมากและแนวคิดก็มีผล)
comco

3
@ v.oddou: ไม่ไม่เป็นไร การสลับจะเปลี่ยนA[i]ไปซึ่งหมายความว่าการทำซ้ำครั้งต่อไปจะไม่เปรียบเทียบค่าสองค่าเดียวกันกับค่าก่อนหน้า ใหม่A[i]จะเหมือนกับของลูปสุดท้ายA[A[i]]แต่ใหม่A[A[i]]จะเป็นค่าใหม่ ลองและดู
caf

128

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


20
;) 4 ขวบของคุณจะต้องเข้าใกล้ 5 หรือ / และเป็นอัจฉริยะ ลูกสาวอายุ 4 ขวบของฉันยังไม่สามารถนับได้อย่างถูกต้องถึง 4 เพื่อความยุติธรรมสมมติว่าเธอเพิ่งจะรวมการมีอยู่ของ "4" เป็นอย่างอื่นจนกระทั่งตอนนี้เธอมักจะข้ามมัน "1,2,3,5,6,7" เป็นลำดับการนับตามปกติของเธอ ฉันขอให้เธอเพิ่มดินสอเข้าด้วยกันและเธอจะจัดการ 1 + 2 = 3 โดยการแยกหมายเลขทั้งหมดอีกครั้งตั้งแต่เริ่มต้น ฉันเป็นห่วงจริง ๆ ... : '(meh ..
v.oddou

วิธีการที่เรียบง่ายและมีประสิทธิภาพ
PabTorre

6
โอ้ (พื้นห้องครัว) ฮ่าฮ่า - แต่จะไม่เป็น O (n ^ 2) เหรอ?

13
O (m²) ฉันเดา :)
Viktor Mellgren

1
@phuclv: คำตอบระบุว่า "นี่มีความต้องการพื้นที่ของ O (พื้นห้องครัว)" แต่ในกรณีใด ๆ นี่เป็นตัวอย่างที่การเรียงลำดับสามารถทำได้ในเวลาO (n) --- ดูการสนทนานี้
Anthony Labarre

36

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

ฉันชอบวิธีแก้ปัญหาง่ายๆ - และฉันก็เชื่อด้วยว่ามันอาจเร็วกว่าการคำนวณผลรวมหรือผลรวมของกำลังสองเป็นต้น


11
ฉันเสนอคำตอบที่ชัดเจน แต่นี่ไม่ใช่สิ่งที่ผู้สัมภาษณ์ต้องการ ฉันพูดอย่างชัดเจนในคำถามว่านี่ไม่ใช่คำตอบที่ฉันต้องการ อีกคำตอบที่ชัดเจน: เรียงลำดับก่อน การO(N)เรียงลำดับการนับและO(N log N)การเปรียบเทียบไม่เป็นสิ่งที่ฉันกำลังมองหาแม้ว่าจะเป็นวิธีแก้ปัญหาที่ง่ายมาก
polygenelubricants

@polygenel น้ำมันหล่อลื่น: ฉันไม่สามารถหาที่คุณพูดในคำถามของคุณ หากคุณพิจารณาว่าบิตเซ็ตเป็นผลลัพธ์แสดงว่าไม่มีการส่งผ่านครั้งที่สอง ความซับซ้อนคือ (ถ้าเราพิจารณาว่า N เป็นค่าคงที่ตามที่ผู้สัมภาษณ์แนะนำโดยบอกว่าความซับซ้อนคือ "นิยามในkไม่ใช่ N") O (1) และถ้าคุณต้องการสร้างผลลัพธ์ที่ "สะอาด" มากขึ้นคุณ รับ O (k) ซึ่งดีที่สุดที่คุณจะได้รับเพราะคุณต้องการ O (k) เสมอเพื่อสร้างผลลัพธ์ที่สะอาด
Chris Lercher

"โปรดทราบว่าฉันไม่ได้กำลังมองหาวิธีการแก้ปัญหาที่ชัดเจน (เช่นใช้ชุดบิต" ย่อหน้าสุดท้ายที่สองจากคำถามเดิม
hrnt

9
@hmt: ใช่คำถามนี้ได้รับการแก้ไขเมื่อไม่กี่นาทีที่ผ่านมา ฉันแค่ให้คำตอบว่าฉันคาดหวังจากผู้ให้สัมภาษณ์ ... การสร้างวิธีแก้ปัญหาย่อยที่ดีที่สุด (คุณไม่สามารถเอาชนะ O (n) + O (k) เวลาไม่ว่าคุณจะทำอะไรก็ตาม) ไม่มีเหตุผลสำหรับฉัน - ยกเว้นในกรณีที่คุณไม่สามารถมีพื้นที่เพิ่มเติม O (n) แต่คำถามนั้นไม่ชัดเจน
Chris Lercher

3
ฉันได้แก้ไขคำถามอีกครั้งเพื่อชี้แจงเพิ่มเติม ฉันขอขอบคุณข้อเสนอแนะ / คำตอบ
polygenelubricants

33

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


15

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

เราสามารถวิเคราะห์ความซับซ้อนของเวลาและพื้นที่ของอัลกอริทึมของ sdcvvc และ Dimitris Andreou

การจัดเก็บ:

l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j  (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)

l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`

ดังนั้น l_j \in \Theta(j log n)

พื้นที่เก็บข้อมูลทั้งหมดที่ใช้: \sum_{j=1}^k l_j \in \Theta(k^2 log n)

พื้นที่ที่ใช้: สมมติว่าการคำนวณa^jต้องใช้ceil(log_2 j)เวลารวมเวลา:

t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n)  \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)

เวลาที่ใช้ทั้งหมด: \Theta(kn log n)

หากเวลาและพื้นที่นี้เป็นที่น่าพอใจคุณสามารถใช้อัลกอริทึมแบบเรียกซ้ำง่าย ๆ ให้ b! i เป็นรายการที่มีอยู่ในถุงและจำนวนของตัวเลขก่อนการถอดและ k คือจำนวนการลบ ในไวยากรณ์ Haskell ...

let
  -- O(1)
  isInRange low high v = (v >= low) && (v <= high)
  -- O(n - k)
  countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
  findMissing l low high krange
    -- O(1) if there is nothing to find.
    | krange=0 = l
    -- O(1) if there is only one possibility.
    | low=high = low:l
    -- Otherwise total of O(knlog(n)) time
    | otherwise =
       let
         mid = (low + high) `div` 2
         klow = countInRange low mid
         khigh = krange - klow
       in
         findMissing (findMissing low mid klow) (mid + 1) high khigh
in
  findMising 1 (n - k) k

ที่เก็บข้อมูลที่ใช้: O(k)สำหรับรายการO(log(n))สำหรับสแต็ก: O(k + log(n)) อัลกอริทึมนี้ใช้งานง่ายมีความซับซ้อนในเวลาเดียวกัน


1
+1 ดูดี แต่คุณทำให้ฉันไปจากบรรทัด 4 ถึงบรรทัด 5 ในตัวอย่าง # 1 - คุณช่วยอธิบายเพิ่มเติมได้ไหม ขอบคุณ!
j_random_hacker

isInRangeคือO (log n)ไม่ใช่O (1) : เปรียบเทียบตัวเลขในช่วง 1..n ดังนั้นจึงต้องเปรียบเทียบO (log n)บิต ฉันไม่รู้ว่าข้อผิดพลาดนี้มีผลกระทบต่อส่วนที่เหลือของการวิเคราะห์อย่างไร
jcsahnwaldt พูดว่า GoFundMonica

14

รอสักครู่ ตามที่ระบุคำถามมีจำนวน 100 หมายเลขในกระเป๋า ไม่ว่าปัญหาจะใหญ่แค่ไหนปัญหาสามารถแก้ไขได้ในเวลาที่คงที่เพราะคุณสามารถใช้ชุดและลบตัวเลขออกจากชุดในการวนซ้ำสูงสุด 100 - k ของลูป 100 เป็นค่าคงที่ ชุดจำนวนที่เหลือคือคำตอบของคุณ

หากเราหาวิธีแก้ปัญหาให้กับตัวเลขตั้งแต่ 1 ถึง N จะไม่มีการเปลี่ยนแปลงใด ๆ ยกเว้น N ไม่ใช่ค่าคงที่ดังนั้นเราจึงอยู่ใน O (N - k) = O (N) เวลา ตัวอย่างเช่นหากเราใช้ชุดบิตเราจะตั้งค่าบิตเป็น 1 ในเวลา O (N) วนซ้ำตามตัวเลขตั้งค่าบิตเป็น 0 เมื่อเราไป (O (Nk) = O (N)) จากนั้นเรา มีคำตอบ

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


9
คำตอบนี้ง่ายเกินไปและเราทุกคนรู้ว่าคำตอบง่ายๆนั้นใช้ไม่ได้! ;) อย่างจริงจังแม้ว่าคำถามดั้งเดิมน่าจะเน้นถึงความต้องการพื้นที่ O (k)
DK

ปัญหาไม่ใช่เรื่องง่าย แต่คุณจะต้องใช้หน่วยความจำเพิ่มเติม O (n) สำหรับแผนที่ ปัญหาทำให้ฉันแก้ไขในเวลาคงที่และหน่วยความจำคงที่
Mojo Risin

3
ฉันพนันได้เลยว่าคุณสามารถพิสูจน์ได้ว่าทางออกที่น้อยที่สุดคืออย่างน้อย O (N) เพราะน้อยกว่านั่นหมายความว่าคุณไม่ได้ดูตัวเลขบางตัวและเนื่องจากไม่มีการสั่งซื้อที่ระบุไว้จึงจำเป็นต้องดูหมายเลขทั้งหมด
v.oddou

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

8

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

  1. แบ่งพาร์ติชันชุดด้วยความเคารพต่อเดือยแบบสุ่มpเข้าไปในพาร์ติชันlซึ่งมีจำนวนที่เล็กกว่าเดือยและrจำนวนที่มากกว่าเดือย

  2. กำหนดว่าพาร์ทิชันใดที่ไม่มีตัวเลข 2 ตัวโดยการเปรียบเทียบค่า pivot กับขนาดของแต่ละพาร์ติชัน ( p - 1 - count(l) = count of missing numbers in lและ n - count(r) - p = count of missing numbers in r)

  3. a) หากแต่ละพาร์ติชั่นหายไปหนึ่งหมายเลขให้ใช้ความแตกต่างของวิธีการหาผลรวมเพื่อหาแต่ละหมายเลขที่หายไป

    (1 + 2 + ... + (p-1)) - sum(l) = missing #1 และ ((p+1) + (p+2) ... + n) - sum(r) = missing #2

    b) หากพาร์ทิชันหนึ่งหายไปทั้งตัวเลขและพาร์ติชั่นนั้นว่างเปล่าดังนั้นหมายเลขที่หายไปนั้นเป็นอย่างใดอย่างหนึ่ง(p-1,p-2)หรือ(p+1,p+2) ขึ้นอยู่กับว่าพาร์ติชั่นนั้นหายไปไหน

    หากพาร์ติชันหนึ่งหายไป 2 หมายเลข แต่ไม่ว่างเปล่าให้แบ่งพาร์ติชันนั้นออก

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

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

<?php

  $list = range(1,100);
  unset($list[3]);
  unset($list[31]);

  findMissing($list,1,100);

  function findMissing($list, $min, $max) {
    if(empty($list)) {
      print_r(range($min, $max));
      return;
    }

    $l = $r = [];
    $pivot = array_pop($list);

    foreach($list as $number) {
      if($number < $pivot) {
        $l[] = $number;
      }
      else {
        $r[] = $number;
      }
    }

    if(count($l) == $pivot - $min - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
    }
    else if(count($l) < $pivot - $min) {
      // more than 1 missing number, recurse
      findMissing($l, $min, $pivot-1);
    }

    if(count($r) == $max - $pivot - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
    } else if(count($r) < $max - $pivot) {
      // mroe than 1 missing number recurse
      findMissing($r, $pivot+1, $max);
    }
  }

การสาธิต


การแบ่งพาร์ติชันชุดนั้นเหมือนกับการใช้พื้นที่เชิงเส้น อย่างน้อยมันจะไม่ทำงานในการตั้งค่าการสตรีม
โทมัส Ahle

@ThomasAhle ดูen.wikipedia.org/wiki/Selection_algorithm#Space_complexity การแบ่งส่วนในชุดต้องใช้พื้นที่เพิ่มเติม O (1) - ไม่ใช่พื้นที่เชิงเส้น ในการตั้งค่าการสตรีมมันจะเป็นพื้นที่เพิ่มเติม O (k) อย่างไรก็ตามคำถามเดิมไม่ได้กล่าวถึงการสตรีม
FuzzyTree

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

1
แต่อย่างที่คุณบอกว่าประสิทธิภาพอาจลดลงเมื่อมีการเพิ่มจำนวนมากขึ้น? นอกจากนี้เรายังสามารถใช้อัลกอริธึมค่ามัธยฐานเชิงเส้นเวลาเพื่อให้ได้ค่าที่สมบูรณ์แบบเสมอ แต่ถ้าตัวเลข k กระจายออกไปใน 1, ... , n, คุณจะไม่ต้องไปเกี่ยวกับ logk ระดับ "ลึก" ก่อนที่คุณจะสามารถตัด สาขาใดบ้าง
โทมัส Ahle

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

7

นี่คือวิธีแก้ปัญหาที่ใช้พื้นที่เก็บข้อมูล k บิตเพิ่มเติมโดยไม่มีลูกเล่นใด ๆ ที่ฉลาดและตรงไปตรงมา เวลาดำเนินการ O (n), พื้นที่พิเศษ O (k) เพียงเพื่อพิสูจน์ว่าสิ่งนี้สามารถแก้ไขได้โดยไม่ต้องอ่านวิธีแก้ปัญหาก่อนหรือเป็นอัจฉริยะ:

void puzzle (int* data, int n, bool* extra, int k)
{
    // data contains n distinct numbers from 1 to n + k, extra provides
    // space for k extra bits. 

    // Rearrange the array so there are (even) even numbers at the start
    // and (odd) odd numbers at the end.
    int even = 0, odd = 0;
    while (even + odd < n)
    {
        if (data [even] % 2 == 0) ++even;
        else if (data [n - 1 - odd] % 2 == 1) ++odd;
        else { int tmp = data [even]; data [even] = data [n - 1 - odd]; 
               data [n - 1 - odd] = tmp; ++even; ++odd; }
    }

    // Erase the lowest bits of all numbers and set the extra bits to 0.
    for (int i = even; i < n; ++i) data [i] -= 1;
    for (int i = 0; i < k; ++i) extra [i] = false;

    // Set a bit for every number that is present
    for (int i = 0; i < n; ++i)
    {
        int tmp = data [i];
        tmp -= (tmp % 2);
        if (i >= even) ++tmp;
        if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
    }

    // Print out the missing ones
    for (int i = 1; i <= n; ++i)
        if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);

    // Restore the lowest bits again.
    for (int i = 0; i < n; ++i) {
        if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
        else { if (data [i] % 2 == 0) data [i] += 1; }
    }
}

ไม่คุณต้องการ(data [n - 1 - odd] % 2 == 1) ++odd;?
ชาร์ลส์

2
คุณอธิบายได้ไหมว่ามันทำงานอย่างไร ฉันไม่เข้าใจ
Teepeemm

การแก้ปัญหาจะง่ายมากถ้าฉันสามารถใช้อาร์เรย์ของ (n + k) booleans สำหรับการจัดเก็บชั่วคราว แต่ไม่ได้รับอนุญาต ดังนั้นฉันจึงจัดเรียงข้อมูลใหม่ใส่ตัวเลขคู่ที่จุดเริ่มต้นและตัวเลขคี่ที่ท้ายอาร์เรย์ ตอนนี้บิตต่ำสุดของตัวเลข n เหล่านั้นสามารถใช้สำหรับการจัดเก็บชั่วคราวได้เพราะฉันรู้ว่ามีเลขคู่และเลขคี่จำนวนเท่าไรและสามารถสร้างบิตต่ำสุดใหม่ได้! บิต n เหล่านี้และ k บิตพิเศษเป็นบูลีน (n + k) ที่ฉันต้องการ
gnasher729

2
สิ่งนี้จะไม่ทำงานหากข้อมูลมีขนาดใหญ่เกินไปที่จะเก็บไว้ในหน่วยความจำและคุณเห็นว่าเป็นกระแสข้อมูลเท่านั้น แฮ็คอร่อย ๆ :)
โธมัส Ahle

ความซับซ้อนของพื้นที่สามารถเป็น O (1) ในรอบแรกคุณจะประมวลผลตัวเลขทั้งหมด <(n - k) ด้วยอัลกอริทึมนี้โดยไม่ต้องใช้ 'ส่วนเสริม' ในรอบที่สองคุณจะล้างบิตพาริตี้อีกครั้งและใช้ตำแหน่ง k แรกสำหรับหมายเลขดัชนี (nk) .. (n)
emu

5

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

S = ผลรวมของตัวเลขทั้งหมดในถุง (S <5050)
Z = ผลรวมของตัวเลขที่หายไป 5050 - S

ถ้าตัวเลขที่หายไปมีxและyแล้ว:

x = Z - y และ
max (x) = Z - 1

ดังนั้นคุณตรวจสอบช่วงจาก1ถึงmax(x)และหาจำนวน


1
หมายเลขคืออะไรmax(x)เมื่อxไหร่
โทมัส Ahle

2
เขาอาจหมายถึงสูงสุดจากชุดของตัวเลข
JavaHopper

หากเรามีตัวเลขมากกว่า 2 ตัวทางออกนี้จะถูกจับ
ozgeneral

4

อาจเป็นอัลกอริทึมนี้สามารถทำงานได้สำหรับคำถามที่ 1:

  1. คำนวณล่วงหน้าจำนวนเต็ม 100 จำนวนแรก (val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. หรือองค์ประกอบที่ยังคงมาจากอินพุตสตรีม (val1 = val1 ^ next_input)
  3. คำตอบสุดท้าย = val ^ val1

หรือดีกว่า:

def GetValue(A)
  val=0
  for i=1 to 100
    do
      val=val^i
    done
  for value in A:
    do
      val=val^value 
    done
  return val

ในความเป็นจริงอัลกอริทึมนี้สามารถขยายได้สำหรับตัวเลขที่หายไปสองตัว ขั้นตอนแรกยังคงเหมือนเดิม เมื่อเราเรียก GetValue ด้วยตัวเลขที่หายไปสองตัวผลลัพธ์จะเป็นa1^a2ตัวเลขที่หายไปสองตัว มาพูดกัน

val = a1^a2

ทีนี้เพื่อแยก a1 และ a2 ออกจาก val เราหาค่าบิตใด ๆ ใน val ให้บอกว่าithบิตตั้งอยู่ในวาล นั่นหมายความว่า a1 และ a2 มีพาริตีต่างกันที่ithตำแหน่งบิต ตอนนี้เราทำซ้ำอีกครั้งในอาร์เรย์เดิมและเก็บค่า xor ไว้สองค่า หนึ่งตัวสำหรับตัวเลขที่มีชุดบิต ith และอื่น ๆ ที่ไม่มีชุดบิตนั้น ตอนนี้เรามีที่เก็บสองถังและตัวเลขรับประกันของมันa1 and a2จะอยู่ในถังที่แตกต่างกัน ทวนซ้ำสิ่งที่เราทำเพื่อค้นหาองค์ประกอบที่หายไปหนึ่งรายการในที่เก็บข้อมูลแต่ละชุด


นี่เป็นเพียงการแก้ปัญหาk=1ใช่มั้ย แต่ฉันชอบใช้xorมากกว่าผลรวมดูเหมือนเร็วขึ้นเล็กน้อย
โทมัส Ahle

@ThomasAhle ใช่ ฉันได้เรียกสิ่งนั้นออกมาในคำตอบของฉัน
bashrc

ขวา. คุณมีความคิดว่าคำสั่งที่สอง "xor อาจเป็นอะไรสำหรับ k = 2? คล้ายกับการใช้กำลังสองเพื่อหาผลรวมเราสามารถ "สี่เหลี่ยม" สำหรับ xor ได้ไหม?
โทมัส Ahle

1
@ThomasAhle แก้ไขเพื่อให้ทำงานได้ 2 หมายเลขที่หายไป
bashrc

นี้เป็นที่ชื่นชอบของฉันวิธี :)
โรเบิร์ตกษัตริย์

3

คุณสามารถแก้ไข Q2 ได้หากคุณมีผลรวมของทั้งสองรายการและผลิตภัณฑ์ของทั้งสองรายการ

(l1 เป็นต้นฉบับ l2 คือรายการที่แก้ไข)

d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)

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

n = len(l1)
d = (n/2)*(n+1) - sum(l2)

ตอนนี้เรารู้แล้วว่า (หาก a และ b เป็นตัวเลขที่ลบออก):

a + b = d
a * b = m

ดังนั้นเราสามารถจัดเรียงใหม่ไปที่:

a = s - b
b * (s - b) = m

และทวีคูณออกมา:

-b^2 + s*b = m

และจัดเรียงใหม่ดังนั้นด้านขวาเป็นศูนย์:

-b^2 + s*b - m = 0

จากนั้นเราสามารถแก้ปัญหาด้วยสูตรสมการกำลังสอง:

b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b

ตัวอย่างโค้ด Python 3:

from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5

ฉันไม่ทราบถึงความซับซ้อนของ sqrt, ลดและรวมฟังก์ชั่นดังนั้นฉันจึงไม่สามารถแก้ไขความซับซ้อนของการแก้ปัญหานี้ (ถ้าใครรู้โปรดแสดงความคิดเห็นด้านล่าง)


ใช้เวลาและหน่วยความจำในการคำนวณx1*x2*x3*...เท่าใด
โทมัส Ahle

@ThomasAhle มันเป็น O (n) - เวลาและ O (1) - เว้นวรรคบนความยาวของรายการ แต่ในความเป็นจริงมันเป็นมากกว่าการคูณ (อย่างน้อยใน Python) คือ O (n ^ 1.6) - เวลาบนความยาวของ หมายเลขและหมายเลขคือ O (log n) -space ตามความยาว
Tuomas Laakkonen

@ThomasAhle ไม่บันทึก (a ^ n) = n * บันทึก (a) ดังนั้นคุณจะมี O (l log k) -space เพื่อเก็บหมายเลข เมื่อให้รายการความยาว l และความยาวดั้งเดิมจำนวน k คุณจะมี O (l) -space แต่ปัจจัยคงที่ (log k) จะต่ำกว่าการเขียนทั้งหมดหมด (ฉันไม่คิดว่าวิธีการของฉันเป็นวิธีที่ดีโดยเฉพาะอย่างยิ่งในการตอบคำถาม)
Tuomas Laakkonen

3

สำหรับไตรมาสที่ 2 นี่เป็นวิธีการแก้ปัญหาที่มีประสิทธิภาพมากกว่าผู้อื่นเล็กน้อย แต่ยังมีรันไทม์ O (N) และใช้พื้นที่ O (k)

แนวคิดคือการเรียกใช้อัลกอริทึมดั้งเดิมสองครั้ง ในคนแรกที่คุณได้รับจำนวนรวมที่หายไปซึ่งจะทำให้คุณมีขอบเขตบนของตัวเลขที่หายไป เรียกหมายเลขนี้Nกัน คุณรู้ว่าตัวเลขสองตัวที่ขาดหายไปนั้นจะรวมกันNดังนั้นตัวเลขแรกจะอยู่ในช่วงเวลา[1, floor((N-1)/2)]ขณะที่ตัวเลขที่สองจะเข้า[floor(N/2)+1,N-1]มา

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

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


Ahaha ใช่นี่เป็นคำตอบเดียวกับที่ฉันคิดไว้ในไตรมาส 2 เพียงแค่คำนวณผลรวมอีกครั้งเพื่อหาค่าลบทั้งหมดที่ต่ำกว่า N / 2 แต่มันดีกว่านี้!
xjcl

2

ฉันคิดว่าสิ่งนี้สามารถทำได้โดยไม่ต้องมีสมการและทฤษฎีทางคณิตศาสตร์ที่ซับซ้อน ด้านล่างนี้เป็นข้อเสนอสำหรับการแก้ปัญหาด้านเวลาและ O (2n):

สมมติฐานการป้อนข้อมูลแบบฟอร์ม:

# ของตัวเลขในถุง = n

# หมายเลขที่หายไป = k

ตัวเลขในถุงถูกแทนด้วยอาร์เรย์ที่มีความยาว n

ความยาวของอินพุตอาร์เรย์สำหรับ algo = n

รายการที่หายไปในอาร์เรย์ (หมายเลขที่นำออกจากกระเป๋า) จะถูกแทนที่ด้วยค่าขององค์ประกอบแรกในอาร์เรย์

เช่น. ถุงเริ่มแรกดูเหมือน [2,9,3,7,8,6,4,5,1,10] ถ้า 4 ถูกนำออกมามูลค่าของ 4 จะกลายเป็น 2 (องค์ประกอบแรกของอาร์เรย์) ดังนั้นหลังจากนำ 4 ออกกระเป๋าจะมีลักษณะเช่น [2,9,3,7,8,6,2,5,1,10]

กุญแจสำคัญในการแก้ปัญหานี้คือการติดแท็ก INDEX ของหมายเลขที่เข้าชมโดยลบค่าที่ INDEX นั้นเนื่องจากมีการสำรวจอาร์เรย์

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }

ใช้หน่วยความจำมากเกินไป
โทมัส Ahle

2

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

  • ทำให้อาร์เรย์ของขนาดau = k^2
  • เลือกใด ๆฟังก์ชันแฮชสากลh : {1,...,n} -> {1,...,u} , (เหมือนทวีคูณ - กะ )
  • สำหรับiในแต่ละที่1, ..., nเพิ่มขึ้นa[h(i)] += i
  • สำหรับแต่ละหมายเลขในสตรีมใส่พร่องxa[h(x)] -= x

หากตัวเลขที่หายไปทั้งหมดถูกแฮชไปยังที่เก็บข้อมูลอื่นองค์ประกอบที่ไม่เป็นศูนย์ของอาร์เรย์จะมีตัวเลขที่หายไป

ความน่าจะเป็นที่คู่ใดคู่หนึ่งถูกส่งไปที่ฝากข้อมูลเดียวกันน้อยกว่า1/uตามคำจำกัดความของฟังก์ชันแฮชสากล เนื่องจากมีเกี่ยวกับคู่เรามีว่าน่าจะเป็นข้อผิดพลาดเป็นอย่างมากk^2/2 k^2/2/u=1/2นั่นคือเราประสบความสำเร็จด้วยความน่าจะเป็นอย่างน้อย 50% และถ้าเราเพิ่มuเราก็เพิ่มโอกาสของเรา

โปรดสังเกตว่าอัลกอริทึมนี้ใช้k^2 lognบิตของพื้นที่ (เราต้องการlognบิตต่ออาร์เรย์ที่เก็บข้อมูล) ซึ่งตรงกับพื้นที่ที่ต้องการโดยคำตอบของ @Dimitris Andreou (โดยเฉพาะความต้องการพื้นที่ของการแยกตัวประกอบพหุนามซึ่งเกิดขึ้นจากการสุ่ม) อัลกอริธึมนี้ก็มีค่าคงที่ เวลาต่อการอัปเดตมากกว่าเวลาkในกรณีของผลรวมของพลังงาน

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


หมายเหตุ: เรายังสามารถใช้งานxorในถังแต่ละชุดแทนได้sumหากทำได้เร็วกว่าบนเครื่องของเรา
โทมัส Ahle

ที่น่าสนใจ แต่ฉันคิดว่าสิ่งนี้เคารพข้อ จำกัด ด้านพื้นที่เมื่อk <= sqrt(n)- อย่างน้อยถ้าu=k^2? สมมติว่า k = 11 และ n = 100 จากนั้นคุณจะมี 121 buckets และอัลกอริทึมจะสิ้นสุดลงคล้ายกับการมีอาร์เรย์ 100 บิตที่คุณตรวจสอบเมื่อคุณอ่าน # จากสตรีม การเพิ่มการuปรับปรุงช่วยเพิ่มโอกาสในการประสบความสำเร็จ แต่มีข้อ จำกัด ว่าคุณสามารถเพิ่มได้มากเท่าไหร่ก่อนที่คุณจะมีพื้นที่ จำกัด
FuzzyTree

1
ฉันคิดว่าปัญหานี้เหมาะสมnกว่าที่kคุณคิดไว้มาก แต่จริงๆแล้วคุณสามารถลดพื้นที่ลงได้k lognด้วยวิธีที่คล้ายกับการแฮ็กที่อธิบายไว้ในขณะที่ยังคงมีการอัปเดตเวลาอย่างต่อเนื่อง มันอธิบายไว้ในgnunet.org/eppstein-set-reconciliationเช่นผลรวมของวิธีการใช้พลังงาน แต่โดยทั่วไปแล้วคุณจะแฮ็คไปที่ถัง 'two k' ที่มีฟังก์ชั่นแฮชที่แข็งแกร่งเช่น hash tabulation ซึ่งรับประกันว่าถังบางส่วนจะมีองค์ประกอบเดียวเท่านั้น . ในการถอดรหัสคุณระบุที่ฝากข้อมูลและลบองค์ประกอบออกจากถังทั้งสองซึ่ง (น่าจะเป็น) เพิ่มที่ฝากข้อมูลอื่นและอื่น ๆ
Thomas Ahle

2

ทางออกที่ง่ายมากสำหรับ Q2 ซึ่งฉันไม่แปลกใจที่ไม่มีใครตอบแล้ว ใช้วิธีการจาก Q1 เพื่อหาผลรวมของตัวเลขที่หายไปสองตัว ลองเขียนแทนด้วย S จากนั้นหนึ่งในตัวเลขที่หายไปนั้นเล็กกว่า S / 2 และอีกอันก็ใหญ่กว่า S / 2 (duh) รวมตัวเลขทั้งหมดจาก 1 ถึง S / 2 และเปรียบเทียบกับผลลัพธ์ของสูตร (คล้ายกับวิธีการในไตรมาสที่ 1) เพื่อหาตัวเลขที่ต่ำกว่าระหว่างตัวเลขที่หายไป ลบออกจาก S เพื่อหาจำนวนที่หายไปที่ใหญ่กว่า


ฉันคิดว่านี่เป็นคำตอบเดียวกับSvalorzenแต่คุณอธิบายด้วยคำพูดที่ดีกว่า มีความคิดว่าจะพูดคุยกับ Qk อย่างไร?
John McClane

ขออภัยที่ไม่มีคำตอบอื่น ๆ ฉันไม่แน่ใจว่าเป็นไปได้หรือไม่ที่จะสรุปให้เป็น $ Q_k $ เนื่องจากในกรณีนี้คุณไม่สามารถผูกองค์ประกอบที่ขาดหายไปให้เล็กที่สุดในบางช่วงได้ คุณรู้หรือไม่ว่าองค์ประกอบบางอย่างจะต้องเล็กกว่า $ S / k $ แต่นั่นอาจเป็นจริงสำหรับองค์ประกอบหลายรายการ
Gilad Deutsch

1

เป็นปัญหาที่ดีมาก ฉันจะใช้ความแตกต่างที่กำหนดไว้สำหรับ Qk ภาษาการเขียนโปรแกรมจำนวนมากสนับสนุนการใช้ภาษาเช่นรูบี:

missing = (1..100).to_a - bag

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


1
ใช้พื้นที่มากเกินไป
โทมัส Ahle

@ThomasAhle: ทำไมคุณเพิ่มความคิดเห็นที่ไร้ประโยชน์กับทุกคำตอบที่สอง? คุณหมายถึงอะไรเพราะใช้พื้นที่มากเกินไป
DarkDust

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

1

คุณอาจจะลองใช้ตัวกรองบลูม ใส่หมายเลขแต่ละหมายเลขในถุงลงในช่องว่างจากนั้นวนซ้ำ 1-k ครบชุดจนกว่าจะไม่พบหมายเลขแต่ละตัว นี่อาจไม่พบคำตอบในทุกสถานการณ์ แต่อาจเป็นคำตอบที่ดีพอ


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

ฮ่าฮ่านี่อาจเป็นหนึ่งในคำตอบที่ใช้งานได้จริง แต่ได้รับความสนใจน้อย
ldog

1

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

ตัวอย่างเช่นอาจเป็นไปได้ว่าผู้สัมภาษณ์กำลังจะส่งnข้อความและจำเป็นต้องรู้kว่าสิ่งนั้นไม่ส่งผลให้มีการตอบกลับและจำเป็นต้องรู้ในเวลาที่นาฬิกาแขวนผนังน้อยที่สุดเท่าที่จะเป็นไปได้หลังจากการn-kตอบกลับมาถึง สมมติว่าลักษณะของช่องข้อความนั้นเป็นแบบที่แม้จะทำงานได้อย่างเต็มรูปแบบมีเวลาพอที่จะทำการประมวลผลระหว่างข้อความโดยไม่มีผลกระทบใด ๆ กับระยะเวลาที่ใช้ในการสร้างผลลัพธ์สุดท้ายหลังจากการตอบกลับครั้งสุดท้ายมาถึง เวลานั้นสามารถนำไปใช้ในการแทรก facet ที่ระบุตัวตนของแต่ละข้อความที่ส่งไปยังชุดและลบออกเมื่อการตอบกลับที่สอดคล้องกันมาถึง เมื่อคำตอบสุดท้ายมาถึงสิ่งเดียวที่ต้องทำคือการลบตัวระบุออกจากชุดซึ่งในการใช้งานทั่วไปจะใช้เวลาO(log k+1)ข้อความและความต้องการที่จะรู้ว่าหลังจากนั้นชุดประกอบด้วยรายการของkองค์ประกอบที่ขาดหายไปและไม่มีการประมวลผลเพิ่มเติมที่จะต้องทำ

O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))นี้อย่างแน่นอนไม่ได้เป็นวิธีที่เร็วที่สุดสำหรับการประมวลผลชุดถุงก่อนสร้างของตัวเลขเพราะสิ่งวิ่งทั้งหมด แต่มันใช้งานได้กับค่าใด ๆ ของk(แม้ว่าจะไม่ทราบล่วงหน้า) และในตัวอย่างข้างต้นมันถูกนำไปใช้ในวิธีที่ช่วยลดช่วงเวลาที่สำคัญที่สุด


ใช้งานได้ไหมถ้าคุณมีหน่วยความจำเสริม O (k ^ 2) เท่านั้น?
โทมัส Ahle

1

คุณสามารถกระตุ้นวิธีแก้ปัญหาโดยคิดในแง่ของสมมาตร (กลุ่มในภาษาคณิตศาสตร์) ไม่ว่าลำดับของตัวเลขจะเป็นอย่างไรคำตอบก็ควรเหมือนกัน หากคุณกำลังจะใช้kฟังก์ชั่นเพื่อช่วยพิจารณาองค์ประกอบที่หายไปคุณควรคิดถึงฟังก์ชั่นที่มีคุณสมบัตินั้น: สมมาตร ฟังก์ชั่นs_1(x) = x_1 + x_2 + ... + x_nเป็นตัวอย่างของฟังก์ชันสมมาตร แต่มีบางอย่างที่มีระดับสูงกว่า โดยเฉพาะอย่างยิ่งพิจารณาหน้าที่สมมาตรประถมศึกษา ฟังก์ชันสมมาตรระดับประถมศึกษาของระดับ 2 คือs_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_nผลรวมของผลิตภัณฑ์ทั้งหมดของสององค์ประกอบ ในทำนองเดียวกันสำหรับฟังก์ชันสมมาตรระดับประถมศึกษาของระดับ 3 และสูงกว่า เห็นได้ชัดว่าสมมาตร ยิ่งกว่านั้นมันกลับกลายเป็นว่าเป็นหน่วยการสร้างสำหรับฟังก์ชันสมมาตรทั้งหมด

s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))คุณสามารถสร้างฟังก์ชั่นสมมาตรประถมศึกษาตามที่คุณไปโดยสังเกตว่า ความคิดต่อไปควรทำให้คุณเชื่อs_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))เช่นนั้นและอื่น ๆ เพื่อให้พวกเขาสามารถคำนวณได้ในครั้งเดียว

เราจะบอกได้อย่างไรว่าไอเท็มใดหายไปจากอาเรย์? (z-x_1)(z-x_2)...(z-x_n)คิดเกี่ยวกับพหุนาม มันประเมิน0หากคุณใส่ในใด ๆ x_iของตัวเลข z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_nขยายพหุนามคุณจะได้รับ ฟังก์ชันสมมาตรระดับประถมศึกษาปรากฏที่นี่เช่นกันซึ่งไม่น่าแปลกใจนักเนื่องจากพหุนามควรอยู่เหมือนเดิมถ้าเราใช้การเรียงสับเปลี่ยนกับราก

ดังนั้นเราสามารถสร้างพหุนามและพยายามแยกมันออกมาเพื่อหาว่าตัวเลขใดไม่ได้อยู่ในเซตเหมือนที่คนอื่นพูดถึง

ในที่สุดถ้าเรามีความกังวลเกี่ยวกับหน่วยความจำล้นที่มีจำนวนมาก (พหุนามสมมาตรที่ n จะเป็นลำดับ100!) เราสามารถทำการคำนวณเหล่านี้mod pโดยที่pเป็นนายกที่ใหญ่กว่า 100 ในกรณีที่เราประเมินพหุนามmod pและพบว่ามันประเมินอีกครั้ง ถึง0เมื่ออินพุตเป็นตัวเลขในชุดและประเมินเป็นค่าที่ไม่เป็นศูนย์เมื่ออินพุตเป็นตัวเลขที่ไม่อยู่ในชุด อย่างไรก็ตามในขณะที่คนอื่น ๆ ได้ออกมาชี้ที่จะได้รับค่าออกจากพหุนามในเวลานั้นขึ้นอยู่กับkที่ไม่ได้เรามีปัจจัยพหุนามNmod p


1

อีกวิธีหนึ่งคือการใช้การกรองกราฟที่เหลือ

สมมติว่าเรามีตัวเลข 1 ถึง 4 และ 3 หายไป การเป็นตัวแทนไบนารีดังต่อไปนี้

1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b

และฉันสามารถสร้างโฟลว์กราฟได้ดังต่อไปนี้

                   1
             1 -------------> 1
             |                | 
      2      |     1          |
0 ---------> 1 ----------> 0  |
|                          |  |
|     1            1       |  |
0 ---------> 0 ----------> 0  |
             |                |
      1      |      1         |
1 ---------> 0 -------------> 1

โปรดทราบว่ากราฟการไหลมีโหนด x ในขณะที่ x เป็นจำนวนบิต และจำนวนสูงสุดของขอบคือ (2 * x) -2

ดังนั้นสำหรับจำนวนเต็ม 32 บิตมันจะใช้พื้นที่ O (32) หรือพื้นที่ O (1)

ตอนนี้ถ้าฉันลบความสามารถของแต่ละหมายเลขเริ่มต้นจาก 1,2,4 แล้วฉันจะเหลือด้วยกราฟที่เหลือ

0 ----------> 1 ---------> 1

ในที่สุดฉันก็จะวิ่งวนเป็นดังนี้

 result = []
 for x in range(1,n):
     exists_path_in_residual_graph(x)
     result.append(x)

ตอนนี้ผลที่ได้จะresultมีตัวเลขที่ไม่ได้หายไปเช่นกัน (บวกเป็นบวก) แต่k <= (ขนาดของผลลัพธ์) <= nเมื่อมีkองค์ประกอบที่ขาดหายไป

ฉันจะผ่านรายการที่กำหนดหนึ่งครั้งเพื่อทำเครื่องหมายผลลัพธ์ที่หายไปหรือไม่

ดังนั้นความซับซ้อนของเวลาจะเป็น O (n)

ในที่สุดก็เป็นไปได้ที่จะลดจำนวนการบวกปลอม (และพื้นที่ที่จำเป็น) โดยการโหน00, 01, 11, 10แทนเพียงและ01


ฉันไม่เข้าใจแผนภาพกราฟของคุณ โหนดขอบและตัวเลขแสดงถึงอะไร? เหตุใดขอบบางเส้นจึงไม่ใช่เส้นอื่น?
Dain

ที่จริงแล้วฉันไม่เข้าใจคำตอบของคุณเลยคุณช่วยอธิบายเพิ่มเติมได้ไหม?
Dain

1

คุณอาจต้องการความกระจ่างเกี่ยวกับความหมายของ O (k)

นี่คือวิธีแก้ปัญหาสำหรับ k โดยพลการสำหรับแต่ละ v ในชุดตัวเลขของคุณสะสมผลรวมของ 2 ^ v ในตอนท้ายวนลูป i จาก 1 ถึง N หากรวม bitwise ANDed กับ 2 ^ i เป็นศูนย์แสดงว่าฉันหายไป (หรือตัวเลขถ้าชั้นของผลรวมหารด้วย 2 ^ i จะเท่ากันหรือsum modulo 2^(i+1)) < 2^i.)

ง่ายใช่มั้ย เวลา O (N), ที่เก็บ O (1) และสนับสนุน k

ยกเว้นว่าคุณกำลังคำนวณจำนวนมหาศาลที่แต่ละเครื่องจะต้องใช้พื้นที่ O (N) ในความเป็นจริงการแก้ปัญหานี้เหมือนกับเวกเตอร์บิต

ดังนั้นคุณสามารถฉลาดและคำนวณผลรวมและผลรวมของกำลังสองและผลรวมของคิวบ์ ... จนถึงผลรวมของ v ^ k และทำเลขทางคณิตศาสตร์แฟนซีเพื่อแยกผลลัพธ์ แต่นั่นก็เป็นจำนวนมากเช่นกันซึ่งทำให้เกิดคำถามว่าเรากำลังพูดถึงรูปแบบนามธรรมอะไร ขนาดพอดีกับพื้นที่ O (1) และใช้เวลานานเท่าใดในการสรุปจำนวนขนาดที่คุณต้องการ


คำตอบที่ดี! สิ่งหนึ่งเล็ก ๆ น้อย ๆ : "ถ้าผลรวมโมดูโล 2 ^ i เป็นศูนย์ดังนั้นฉันหายไป" ไม่ถูกต้อง แต่ชัดเจนว่ามีจุดประสงค์อะไร ฉันคิดว่า "ถ้าผลรวมโมดูโล 2 ^ (i + 1) น้อยกว่า 2 ^ i ดังนั้นฉันหายไป" จะถูกต้อง (แน่นอนว่าในภาษาการเขียนโปรแกรมส่วนใหญ่เราจะใช้การเลื่อนบิตแทนการคำนวณแบบโมดูโลบางครั้งภาษาการเขียนโปรแกรมนั้นมีความหมายมากกว่าภาษาคณิตศาสตร์ปกติ :-))
jcsahnwaldt พูดว่า GoFundMonica

1
ขอบคุณคุณพูดถูก! แก้ไขแม้ว่าฉันขี้เกียจและหลงทางจากสัญกรณ์คณิตศาสตร์ ... โอ้และฉันก็ทำมันยุ่งเกินไป แก้ไขอีกครั้ง ...
sfink

1

นี่คือวิธีแก้ปัญหาที่ไม่ต้องพึ่งพาคณิตศาสตร์ที่ซับซ้อนเหมือนคำตอบของ sdcvvc / Dimitris Andreou ไม่ต้องเปลี่ยนอาร์เรย์อินพุตเหมือนที่ caf และ Colonel Panic ทำและไม่ใช้บิตเซ็ตขนาดใหญ่อย่าง Chris Lercher, JeremyP และ หลายคนทำ โดยพื้นฐานแล้วฉันเริ่มต้นด้วยแนวคิดของ Svalorzen's / Gilad Deutch สำหรับไตรมาสที่ 2 โดยทั่วไปจะเป็นกรณีทั่วไป Qk และนำไปใช้ใน Java เพื่อพิสูจน์ว่าอัลกอริทึมทำงาน

ความคิด

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

ตอนนี้เรามองไปที่SและQ หากQ = 1ก็หมายความว่าแล้วฉันมีเพียงหนึ่งของตัวเลขที่หายไปและจำนวนนี้เป็นอย่างชัดเจนS เราทำเครื่องหมายว่าฉันเสร็จแล้ว (มันถูกเรียกว่า "โปร่งใส" ในโปรแกรม) และปล่อยให้มันออกมาจากการพิจารณาต่อไป ในทางตรงกันข้ามถ้าถาม> 1เราสามารถคำนวณค่าเฉลี่ยของA = S / Qของตัวเลขที่หายไปอยู่ในฉัน เป็นตัวเลขที่ทุกคนมีความแตกต่างกันอย่างน้อยหนึ่งของตัวเลขดังกล่าวเป็นอย่างเคร่งครัดน้อยกว่าและอย่างน้อยหนึ่งอย่างเคร่งครัดมากกว่า ตอนนี้เราแยกฉันในออกเป็นสองช่วงเวลาเล็ก ๆ ซึ่งแต่ละอันจะมีหมายเลขที่ขาดหายไปอย่างน้อยหนึ่งหมายเลข โปรดทราบว่าไม่สำคัญว่าช่วงใดที่เรากำหนดAในกรณีที่เป็นจำนวนเต็ม

เราจะให้ผ่านอาร์เรย์ต่อไปคำนวณSและQสำหรับแต่ละช่วงเวลาที่แยกต่างหาก ( แต่ในการส่งผ่านเดียวกัน) และหลังจากนั้นช่วงเวลาที่มีเครื่องหมายQ = 1และช่วงเวลาที่แยกกับQ> 1 เราทำกระบวนการนี้ต่อไปจนกว่าจะไม่มีช่วง "กำกวม" ใหม่นั่นคือเราไม่มีอะไรจะแยกเพราะแต่ละช่วงเวลามีหมายเลขที่ขาดหายไปหนึ่งหมายเลข (และเรารู้หมายเลขนี้เสมอเพราะเรารู้S ) เราเริ่มจากช่วง "ช่วงทั้งหมด" แต่เพียงผู้เดียวที่มีตัวเลขที่เป็นไปได้ทั้งหมด (เช่น[1..N]ในคำถาม)

การวิเคราะห์ความซับซ้อนของเวลาและพื้นที่

จำนวนรวมของผ่านหน้าเราต้องทำให้จนกว่ากระบวนการหยุดไม่เคยมีตัวเลขที่สูงกว่าที่หายไปนับk ความไม่เท่าเทียมกันp <= kสามารถพิสูจน์ได้อย่างจริงจัง บนมืออื่น ๆ , นอกจากนี้ยังมีการทดลองที่ถูกผูกไว้บนp <ล็อก2 N + 3ที่เป็นประโยชน์สำหรับค่าที่มีขนาดใหญ่ของk เราจำเป็นต้องทำการค้นหาแบบไบนารี่สำหรับแต่ละอินพุทของอาเรย์เพื่อกำหนดช่วงเวลาที่เป็นของมัน นี่เป็นการเพิ่มตัวคูณlog kเพื่อความซับซ้อนของเวลา

รวมเวลาซับซ้อนคือO (n ᛫นาที (k เข้าสู่ระบบ N) ᛫บันทึก k) โปรดทราบว่าสำหรับขนาดใหญ่kนี้เป็นอย่างดีกว่าที่ sdcvvc / วิธี Dimitris Andreou ซึ่งเป็นO (n ᛫ k)

สำหรับการทำงานอัลกอริทึมต้องใช้พื้นที่เพิ่มเติมO (k)สำหรับการจัดเก็บในช่วงเวลาkเกือบทุกครั้งซึ่งดีกว่าO (N) อย่างมากในโซลูชัน "บิตเซ็ต"

การติดตั้ง Java

นี่คือคลาส Java ที่ใช้อัลกอริทึมข้างต้น มันจะส่งกลับอาร์เรย์ที่เรียงลำดับของตัวเลขที่หายไปเสมอ นอกจากนั้นมันไม่จำเป็นต้องมีตัวเลขที่หายไปนับkเพราะมันจะคำนวณในรอบแรก ช่วงทั้งหมดของตัวเลขถูกกำหนดโดยminNumberและmaxNumberพารามิเตอร์ (เช่น 1 และ 100 สำหรับตัวอย่างแรกในคำถาม)

public class MissingNumbers {
    private static class Interval {
        boolean ambiguous = true;
        final int begin;
        int quantity;
        long sum;

        Interval(int begin, int end) { // begin inclusive, end exclusive
            this.begin = begin;
            quantity = end - begin;
            sum = quantity * ((long)end - 1 + begin) / 2;
        }

        void exclude(int x) {
            quantity--;
            sum -= x;
        }
    }

    public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
        Interval full = new Interval(minNumber, ++maxNumber);
        for (inputBag.startOver(); inputBag.hasNext();)
            full.exclude(inputBag.next());
        int missingCount = full.quantity;
        if (missingCount == 0)
            return new int[0];
        Interval[] intervals = new Interval[missingCount];
        intervals[0] = full;
        int[] dividers = new int[missingCount];
        dividers[0] = minNumber;
        int intervalCount = 1;
        while (true) {
            int oldCount = intervalCount;
            for (int i = 0; i < oldCount; i++) {
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    if (itv.quantity == 1) // number inside itv uniquely identified
                        itv.ambiguous = false;
                    else
                        intervalCount++; // itv will be split into two intervals
            }
            if (oldCount == intervalCount)
                break;
            int newIndex = intervalCount - 1;
            int end = maxNumber;
            for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
                // newIndex always >= oldIndex
                Interval itv = intervals[oldIndex];
                int begin = itv.begin;
                if (itv.ambiguous) {
                    // split interval itv
                    // use floorDiv instead of / because input numbers can be negative
                    int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
                    intervals[newIndex--] = new Interval(mean, end);
                    intervals[newIndex--] = new Interval(begin, mean);
                } else
                    intervals[newIndex--] = itv;
                end = begin;
            }
            for (int i = 0; i < intervalCount; i++)
                dividers[i] = intervals[i].begin;
            for (inputBag.startOver(); inputBag.hasNext();) {
                int x = inputBag.next();
                // find the interval to which x belongs
                int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
                if (i < 0)
                    i = -i - 2;
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    itv.exclude(x);
            }
        }
        assert intervalCount == missingCount;
        for (int i = 0; i < intervalCount; i++)
            dividers[i] = (int)intervals[i].sum;
        return dividers;
    }
}

เพื่อความเป็นธรรมคลาสนี้รับอินพุตในรูปแบบของNumberBagวัตถุ NumberBagไม่อนุญาตการแก้ไขอาเรย์และการเข้าถึงแบบสุ่มและยังนับจำนวนครั้งที่อาเรย์ถูกร้องขอสำหรับการสำรวจตามลำดับ นอกจากนี้ยังเหมาะสำหรับการทดสอบอาร์เรย์ขนาดใหญ่มากกว่าIterable<Integer>เพราะมันหลีกเลี่ยงการชกมวยintค่าดั้งเดิมและช่วยให้การห่อส่วนใหญ่int[]เพื่อการเตรียมการทดสอบที่สะดวก มันไม่ยากที่จะแทนที่ถ้าต้องการNumberBagโดยint[]หรือIterable<Integer>พิมพ์ในfindลายเซ็นโดยการเปลี่ยนสองห่วงสำหรับมันเป็นคนก่อน

import java.util.*;

public abstract class NumberBag {
    private int passCount;

    public void startOver() {
        passCount++;
    }

    public final int getPassCount() {
        return passCount;
    }

    public abstract boolean hasNext();

    public abstract int next();

    // A lightweight version of Iterable<Integer> to avoid boxing of int
    public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) {
        return new NumberBag() {
            int index = toIndex;

            public void startOver() {
                super.startOver();
                index = fromIndex;
            }

            public boolean hasNext() {
                return index < toIndex;
            }

            public int next() {
                if (index >= toIndex)
                    throw new NoSuchElementException();
                return base[index++];
            }
        };
    }

    public static NumberBag fromArray(int[] base) {
        return fromArray(base, 0, base.length);
    }

    public static NumberBag fromIterable(Iterable<Integer> base) {
        return new NumberBag() {
            Iterator<Integer> it;

            public void startOver() {
                super.startOver();
                it = base.iterator();
            }

            public boolean hasNext() {
                return it.hasNext();
            }

            public int next() {
                return it.next();
            }
        };
    }
}

การทดสอบ

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

import java.util.*;

public class SimpleTest {
    public static void main(String[] args) {
        int[] input = { 7, 1, 4, 9, 6, 2 };
        NumberBag bag = NumberBag.fromArray(input);
        int[] output = MissingNumbers.find(1, 10, bag);
        System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n",
                Arrays.toString(input), Arrays.toString(output), bag.getPassCount());

        List<Integer> inputList = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            inputList.add(2 * i);
        Collections.shuffle(inputList);
        bag = NumberBag.fromIterable(inputList);
        output = MissingNumbers.find(0, 19, bag);
        System.out.format("%nInput: %s%nMissing numbers: %s%nPass count: %d%n",
                inputList, Arrays.toString(output), bag.getPassCount());

        // Sieve of Eratosthenes
        final int MAXN = 1_000;
        List<Integer> nonPrimes = new ArrayList<>();
        nonPrimes.add(1);
        int[] primes;
        int lastPrimeIndex = 0;
        while (true) {
            primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
            int p = primes[lastPrimeIndex]; // guaranteed to be prime
            int q = p;
            for (int i = lastPrimeIndex++; i < primes.length; i++) {
                q = primes[i]; // not necessarily prime
                int pq = p * q;
                if (pq > MAXN)
                    break;
                nonPrimes.add(pq);
            }
            if (q == p)
                break;
        }
        System.out.format("%nSieve of Eratosthenes. %d primes up to %d found:%n",
                primes.length, MAXN);
        for (int i = 0; i < primes.length; i++)
            System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
    }
}

การทดสอบอาร์เรย์ขนาดใหญ่สามารถทำได้ด้วยวิธีนี้:

import java.util.*;

public class BatchTest {
    private static final Random rand = new Random();
    public static int MIN_NUMBER = 1;
    private final int minNumber = MIN_NUMBER;
    private final int numberCount;
    private final int[] numbers;
    private int missingCount;
    public long finderTime;

    public BatchTest(int numberCount) {
        this.numberCount = numberCount;
        numbers = new int[numberCount];
        for (int i = 0; i < numberCount; i++)
            numbers[i] = minNumber + i;
    }

    private int passBound() {
        int mBound = missingCount > 0 ? missingCount : 1;
        int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
        return Math.min(mBound, nBound);
    }

    private void error(String cause) {
        throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
    }

    // returns the number of times the input array was traversed in this test
    public int makeTest(int missingCount) {
        this.missingCount = missingCount;
        // numbers array is reused when numberCount stays the same,
        // just Fisher–Yates shuffle it for each test
        for (int i = numberCount - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            if (i != j) {
                int t = numbers[i];
                numbers[i] = numbers[j];
                numbers[j] = t;
            }
        }
        final int bagSize = numberCount - missingCount;
        NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
        finderTime -= System.nanoTime();
        int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
        finderTime += System.nanoTime();
        if (inputBag.getPassCount() > passBound())
            error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
        if (found.length != missingCount)
            error("wrong result length");
        int j = bagSize; // "missing" part beginning in numbers
        Arrays.sort(numbers, bagSize, numberCount);
        for (int i = 0; i < missingCount; i++)
            if (found[i] != numbers[j++])
                error("wrong result array, " + i + "-th element differs");
        return inputBag.getPassCount();
    }

    public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
        BatchTest t = new BatchTest(numberCount);
        System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
        for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
            int minPass = Integer.MAX_VALUE;
            int passSum = 0;
            int maxPass = 0;
            t.finderTime = 0;
            for (int j = 1; j <= repeats; j++) {
                int pCount = t.makeTest(missingCount);
                if (pCount < minPass)
                    minPass = pCount;
                passSum += pCount;
                if (pCount > maxPass)
                    maxPass = pCount;
            }
            System.out.format("║ %9d  %9d  ║  %2d  %5.2f  %2d  ║  %11.3f    ║%n", missingCount, numberCount, minPass,
                    (double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
        }
    }

    public static void main(String[] args) {
        System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
        System.out.println("║      Number count     ║      Passes     ║  Average time   ║");
        System.out.println("║   missimg     total   ║  min  avg   max ║ per search (ms) ║");
        long time = System.nanoTime();
        strideCheck(100, 0, 100, 1, 20_000);
        strideCheck(100_000, 2, 99_998, 1_282, 15);
        MIN_NUMBER = -2_000_000_000;
        strideCheck(300_000_000, 1, 10, 1, 1);
        time = System.nanoTime() - time;
        System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
        System.out.format("%nSuccess. Total time: %.2f s.%n", time * 1e-9);
    }
}

ลองใช้กับ Ideone


0

ฉันเชื่อว่าฉันมีอัลกอริทึมO(k)เวลาและO(log(k))พื้นที่เนื่องจากคุณมีfloor(x)และlog2(x)ฟังก์ชันสำหรับจำนวนเต็มขนาดใหญ่โดยพลการ:

คุณมีkจำนวนเต็มยาวบิต (ดังนั้นlog8(k)ช่องว่าง) ที่คุณเพิ่มx^2โดยที่ x คือหมายเลขถัดไปที่คุณพบในกระเป๋า: s=1^2+2^2+...ต้องใช้O(N)เวลา (ซึ่งไม่ใช่ปัญหาสำหรับผู้สัมภาษณ์) ในตอนท้ายคุณจะได้รับj=floor(log2(s))ซึ่งเป็นจำนวนที่ใหญ่ที่สุดที่คุณกำลังมองหา จากนั้นs=s-jและคุณทำอีกครั้งข้างต้น:

for (i = 0 ; i < k ; i++)
{
  j = floor(log2(s));
  missing[i] = j;
  s -= j;
}

ตอนนี้คุณมักจะไม่มีฟังก์ชัน floor และ log2 สำหรับ2756-bit จำนวนเต็ม แต่แทนที่จะเป็น double ดังนั้น? เพียงแค่สำหรับแต่ละ 2 ไบต์ (หรือ 1 หรือ 3 หรือ 4) คุณสามารถใช้ฟังก์ชั่นเหล่านี้เพื่อให้ได้ตัวเลขที่ต้องการ แต่สิ่งนี้จะเพิ่มO(N)ความซับซ้อนให้กับเวลา


0

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

ดังนั้นเมื่อคุณเห็นตัวเลขทั้งหมดให้มองหาตัวเลขที่หายไป เช่นเดียวกับเมื่อตัวเลขสองตัวหายไป เรียบง่ายฉันคิดว่า ไม่มีจุดในการใช้สมการเมื่อคุณเห็นตัวเลขที่เหลืออยู่ในกระเป๋า


2
ฉันคิดว่าข้อดีของการรวมเข้าด้วยกันคือคุณไม่ต้องจำตัวเลขที่คุณเคยเห็นมาแล้ว (เช่นไม่มีข้อกำหนดด้านหน่วยความจำเพิ่มเติม) มิฉะนั้นตัวเลือกเดียวคือการรักษาชุดของค่าทั้งหมดที่เห็นและทำซ้ำผ่านชุดนั้นอีกครั้งเพื่อหาสิ่งที่ขาดหายไป
ด่านเต่า

3
คำถามนี้มักจะถูกถามด้วยข้อกำหนดของความซับซ้อนของพื้นที่ O (1)

ผลรวมของตัวเลข N แรกคือ N (N + 1) / 2 สำหรับ N = 100, Sum = 100 * (101) / 2 = 5050;
tmarthal

0

ฉันคิดว่านี่สามารถทำให้เป็นแบบทั่วไปได้:

แสดงว่า S, M เป็นค่าเริ่มต้นสำหรับผลรวมของชุดเลขคณิตและการคูณ

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

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

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

เมื่อคุณรู้ S1, M1, M และ S, สมการข้างบนนั้นสามารถหา a และ b, ตัวเลขที่หายไปได้

ตอนนี้สำหรับตัวเลขสามตัวที่หายไป:

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

ทีนี้คุณไม่ทราบคือ 3 ในขณะที่คุณมีสมการสองสมการที่คุณสามารถแก้ได้


การคูณมีขนาดค่อนข้างใหญ่ .. นอกจากนี้คุณจะพูดถึงตัวเลขที่หายไปมากกว่า 2 ตัวได้อย่างไร
โทมัส Ahle

ฉันลองสูตรเหล่านี้ตามลำดับที่ง่ายมากโดยมี N = 3 และไม่มีตัวเลข = {1, 2} ฉันไม่ทำงานเพราะฉันเชื่อว่าข้อผิดพลาดอยู่ในสูตร (2) ซึ่งควรอ่านM1 = M / (a * b)(ดูคำตอบนั้น ) จากนั้นก็ใช้งานได้ดี
dma_k

0

ฉันไม่รู้ว่ามันมีประสิทธิภาพหรือไม่ แต่ฉันอยากจะแนะนำวิธีแก้ปัญหานี้

  1. คำนวณ xor ของ 100 องค์ประกอบ
  2. คำนวณ xor ขององค์ประกอบ 98 (หลังจากลบ 2 องค์ประกอบ)
  3. ทีนี้ (ผลลัพธ์ของ 1) XOR (ผลลัพธ์ของ 2) ให้ xor ของ nos ที่หายไปสองตัว i..ea XOR b ถ้า a และ b เป็นองค์ประกอบที่ขาดหายไป
    4. รับผลรวมของ Nos ที่หายไปด้วยวิธีปกติของคุณ สูตรรวมต่างกันและสมมุติว่า diff คือ d

ทีนี้ลองวนซ้ำเพื่อให้ได้คู่ที่เป็นไปได้ (p, q) ซึ่งทั้งคู่อยู่ใน [1, 100] และผลรวมเป็น d

เมื่อได้รับคู่ตรวจสอบว่า (ผลจาก 3) XOR p = q และถ้าใช่เราจะทำ

โปรดแก้ไขให้ฉันด้วยถ้าฉันผิดและให้ความเห็นเกี่ยวกับความซับซ้อนของเวลาด้วยถ้าสิ่งนี้ถูกต้อง


2
ฉันไม่คิดว่าผลรวมและ xor จะกำหนดตัวเลขสองตัวโดยไม่ซ้ำกัน การวนลูปเพื่อให้ k-tuples ที่เป็นไปได้ทั้งหมดที่ผลรวมถึง d ต้องใช้เวลา O (C (n, k-1)) = O (n <sup> k-1 </sup>) ซึ่งสำหรับ k> 2 ไม่ดี.
Teepeemm

0

เราสามารถทำQ1 และ Q2ในO (log n) ได้เกือบตลอดเวลา

สมมติว่าเราmemory chipประกอบด้วยอาร์เรย์ของจำนวนn test tubesและจำนวนหนึ่งxในหลอดทดลองนั้นแสดงด้วยx milliliterสารเคมี - ของเหลว

laser lightสมมติว่าหน่วยประมวลผลของเราเป็น เมื่อเราเปิดไฟเลเซอร์มันจะเคลื่อนที่ไปตามท่อทุกแนวตั้งฉากกับความยาว ทุกครั้งที่มันผ่านสารเคมีเหลว, 1ความสว่างจะลดลง O(1)และผ่านแสงที่เครื่องหมายมิลลิลิตรบางอย่างคือการดำเนินการของ

ทีนี้ถ้าเราส่องแสงเลเซอร์ของเราตรงกลางหลอดทดลองและรับเอาท์พุทของความส่องสว่าง

  • เท่ากับค่าที่คำนวณไว้ล่วงหน้า (คำนวณเมื่อไม่มีตัวเลขหายไป) ดังนั้นจำนวนที่หายไปนั้นมากกว่า n/2แล้วตัวเลขที่หายไปมากกว่า
  • n/2หากผลผลิตของเรามีขนาดเล็กนั้นมีเป็นจำนวนที่ขาดหายไปอย่างน้อยหนึ่งที่มีขนาดเล็กกว่า นอกจากนี้เรายังสามารถตรวจสอบว่าความสว่างลดลง1หรือ2ไม่ ถ้ามันจะลดลง1แล้วจำนวนหนึ่งที่ขาดหายไปมีขนาดเล็กกว่าn/2และอื่น ๆ n/2ที่มีขนาดใหญ่กว่า ถ้ามันจะลดลงแล้วทั้งตัวเลขมีขนาดเล็กกว่า2n/2

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

อัลกอริธึมแบบขนานที่ควรค่าแก่การกล่าวถึง (เพราะน่าสนใจ)

  • การเรียงลำดับโดยอัลกอริทึมแบบขนานบางอย่างเช่นการผสานแบบขนานสามารถทำได้ในO(log^3 n)เวลา แล้วจำนวนที่ขาดหายไปสามารถค้นหาได้โดยการค้นหาแบบไบนารีในO(log n)เวลา
  • ในทางทฤษฎีถ้าเรามีnโปรเซสเซอร์แล้วแต่ละกระบวนการสามารถตรวจสอบหนึ่งในอินพุตและตั้งค่าสถานะบางอย่างที่ระบุหมายเลข (สะดวกในอาร์เรย์) และในขั้นตอนถัดไปแต่ละกระบวนการสามารถตรวจสอบแต่ละแฟล็กและสุดท้ายเอาท์พุทจำนวนที่ไม่ได้ตั้งค่าสถานะ กระบวนการทั้งหมดจะใช้O(1)เวลา มีO(n)ความต้องการพื้นที่ / หน่วยความจำเพิ่มเติม

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


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

1
สำหรับวิธีการจัดเรียงของคุณนั้นจะใช้พื้นที่พิเศษที่ขึ้นอยู่กับNและมากกว่าO(N)เวลา (ในแง่ของการพึ่งพาของมันN) ซึ่งเราตั้งใจจะทำได้ดีกว่า
SirGuy

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