การหมุนคำศัพท์เล็กที่สุดของสตริงโดยใช้อาร์เรย์ต่อท้ายใน O (n)


9

ฉันจะอ้างอิงปัญหาจาก ACM 2003:

พิจารณาสตริงที่มีความยาว n (1 <= n <= 100000) กำหนดการหมุนเวียนคำศัพท์ขั้นต่ำสุด ตัวอย่างเช่นการหมุนของสตริง“ alabala” คือ:

alabala

labalaa

abalaal

balaala

alaalab

laalaba

aalabal

และที่เล็กที่สุดในหมู่พวกเขาคือ "aalabal"

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

ฉันสนใจปัญหานี้มากและฉันก็ยังหาวิธีแก้ปัญหาไม่ได้ ฉันสนใจในแนวคิดและวิธีการแก้ไขปัญหามากกว่าการใช้งานอย่างเป็นรูปธรรม

หมายเหตุ: การหมุนต่ำสุดหมายถึงในลำดับเดียวกันกับในพจนานุกรมภาษาอังกฤษ - "dwor" อยู่ก่อน "word" เพราะ d อยู่ก่อน w

แก้ไข: การสร้างอาร์เรย์ต่อท้ายใช้เวลา O (N)

แก้ไขล่าสุด: ฉันคิดว่าฉันพบทางออก !!! ถ้าฉันเพิ่งรวมสองสาย ดังนั้นถ้าสตริงเป็น "alabala" สตริงใหม่จะให้ฉัน "alabalaalabala" และตอนนี้ฉันจะสร้างอาร์เรย์ต่อท้ายของนี้ (ใน O (2n) = O (n)) และได้รับส่วนต่อท้ายแรกหรือไม่ ฉันเดาว่านี่อาจจะถูก คุณคิดอย่างไร? ขอบคุณ!


คุณจะกำหนด "ขั้นต่ำ" ได้อย่างไร ตัวชี้วัดที่ใช้คืออะไร (อาจชัดเจน แต่ฉันไม่ใช่ผู้เชี่ยวชาญ)
Giorgio

ขอบคุณสำหรับข้อความ! ฉันคิดว่าการหมุนต้องน้อยที่สุด (ออฟเซ็ตต่ำสุด) ไม่ใช่ผลลัพธ์ของการหมุนตามลำดับคำศัพท์
Giorgio

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

ฉันคิดว่าความคิดในการทำซ้ำสตริงเดิมสองครั้งดีมาก! จากนั้นคุณสามารถสร้างอาร์เรย์ต่อท้ายใน O (2n) = O (n) แต่คุณไม่จำเป็นต้องจัดเรียงเพื่อค้นหาขั้นต่ำหรือไม่ สิ่งนี้ต้องการมากกว่า O (n) ใช่ไหม
Giorgio

@Giorgio ดีอาร์เรย์ต่อท้ายที่ตัวเองถือพอเพียงเรียงแล้ว และโน้ตอีกอันอาจจะผิดปกติเล็กน้อย - อย่าลืมว่าการเรียงลำดับสามารถทำได้แม้ใน o (n) โดยมีข้อสันนิษฐานบางอย่างสำหรับวัตถุที่เรียง (ดูตัวอย่างการเรียงตัวของ Radix)
Tomy

คำตอบ:


5

เคล็ดลับง่ายๆในการสร้างการหมุนทั้งหมดของสตริงที่มีความยาว N คือการเชื่อมสตริงด้วยตัวเอง

จากนั้นสตริงย่อยความยาว N ทุกตัวของสตริงความยาว 2N นี้จะเป็นการหมุนของสตริงดั้งเดิม

การค้นหาสตริงย่อย "lexicographically minimal" จะถูกสร้างด้วยโครงสร้าง O (N) ของคุณ


0

ฉันค่อนข้างมั่นใจว่าข้อมูลที่อยู่ในอาเรย์ต่อท้ายนั้นไม่เพียงพอที่จะช่วยให้คุณไปถึง O (n) แต่ส่วนใหญ่สามารถช่วยคุณในการ O (n log n) ลองพิจารณาตระกูลคำต่อท้ายนี้:

a
aba
abacaba
abacabadabacaba
abacabadabacabaeabacabadabacaba
...

คุณสร้างคำต่อท้ายถัดไปโดยใช้คำต่อท้ายก่อนหน้า (พูด aba) เพิ่มอักขระถัดไปที่ยังไม่ได้ใช้แล้วเพิ่มคำต่อท้ายก่อนหน้าอีกครั้ง (so aba -> aba c aba)

ตอนนี้ให้พิจารณาสตริงเหล่านี้ (เพิ่มพื้นที่เพื่อเน้น แต่ไม่ได้เป็นส่วนหนึ่งของสตริง):

ad abacaba
bd abacaba
cd abacaba

สำหรับทั้งสามสตริงการเริ่มต้นของอาร์เรย์ต่อท้ายจะมีลักษณะดังนี้:

a
aba
abacaba
(other suffixes)

ดูคุ้น ๆ เหรอ? สตริงของหลักสูตรเหล่านี้ได้รับการปรับแต่งเพื่อสร้างอาร์เรย์ส่วนต่อท้ายนี้ ตอนนี้ขึ้นอยู่กับตัวอักษรเริ่มต้น (a, b หรือ c) ดัชนี 'ถูกต้อง' (วิธีแก้ปัญหาของคุณ) เป็นตัวแรกตัวที่สองหรือตัวต่อท้ายที่สามในรายการด้านบน

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

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

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


0

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

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

แก้ไข: "ตัวละครตัวหนึ่งกับตัวละครอื่น" อาจไม่ได้เป็นเช่นนั้นมันอาจจะเป็นมากกว่าหนึ่งตัวอักษร แต่โดยรวมแล้วคุณไม่ได้ตรวจสอบมากกว่า n ตัวอักษรผ่านกระบวนการค้นหาทั้งหมดดังนั้นจึงเป็น O (n)

หลักฐานสั้น: คุณตรวจสอบตัวอักษรต่อท้ายเมื่อk 1 มีความยาวมากกว่าคำต่อท้ายkและคุณหยุดและพบว่าวิธีการแก้ปัญหาของคุณถ้าต่อท้ายk +1 สั้นกว่าต่อท้ายk (แล้วคุณจะรู้คำต่อท้ายkเป็นหนึ่งคนที่คุณพยายามหา) ดังนั้นคุณจะตรวจสอบตัวละครในขณะที่คุณอยู่ในลำดับของคำต่อท้ายที่เพิ่มขึ้น เนื่องจากคุณตรวจสอบอักขระส่วนเกินเท่านั้นคุณจึงไม่สามารถตรวจสอบอักขระเกิน n ตัวได้

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

EDIT3: ไม่มันไม่ได้ถือ "abaaa" มีตารางต่อท้าย "a", "aa", "aaa", "abaaa", "baaa" แต่บางทีแนวความคิดนี้อาจนำไปสู่การแก้ปัญหาได้ในที่สุดรายละเอียดเพิ่มเติมบางอย่างจะต้องซับซ้อนกว่านี้ คำถามหลักคือว่ามันเป็นไปได้หรือไม่ที่จะทำการเปรียบเทียบดังกล่าวโดยการตรวจสอบตัวละครน้อยลงดังนั้นจึงเป็น O (n) ทั้งหมดซึ่งฉันเชื่อว่าอาจเป็นไปได้ ฉันไม่สามารถบอกได้ว่าตอนนี้


0

ปัญหา:

สตริงย่อยแบบวงกลมน้อยที่สุดของ Lexicographically เป็นปัญหาของการค้นหาการหมุนของสตริงที่มีคำสั่งพจนานุกรมที่ต่ำที่สุดของการหมุนดังกล่าวทั้งหมด ตัวอย่างเช่นการหมุนด้วยคำน้อยที่สุดของ "bbaaccaadd" จะเป็น "aaccaaddbb"

สารละลาย:

ขั้นตอนวิธีเวลา (n) ถูกเสนอโดย Jean Pierre Duval (1983)

กำหนดสองดัชนีiและjอัลกอริธึมของ Duval เปรียบเทียบส่วนของสตริงที่มีความยาวj - iเริ่มต้นที่iและj(เรียกว่า"duel" ) หากindex + j - iมากกว่าความยาวของสตริงส่วนจะเกิดขึ้นโดยการล้อมรอบ

ตัวอย่างเช่นพิจารณา s = "baabbaba", i = 5 และ j = 7 เนื่องจาก j - i = 2 ส่วนแรกที่เริ่มต้นที่ i = 5 คือ "ab" ส่วนที่สองเริ่มต้นที่ j = 7 ถูกสร้างขึ้นโดยล้อมรอบและยังเป็น "ab" หากสตริงมีความเหมือนกันของคำศัพท์เช่นในตัวอย่างข้างต้นเราจะเลือกสตริงที่เริ่มต้นที่ฉันเป็นผู้ชนะซึ่งก็คือ i = 5

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

ความซับซ้อนของเวลา:

การทำซ้ำครั้งแรกเปรียบเทียบ n สายแต่ละความยาว 1 (n / 2 การเปรียบเทียบ), การทำซ้ำครั้งที่สองอาจเปรียบเทียบ n / 2 สายยาว 2 (n / 2 เปรียบเทียบ), และต่อไปเรื่อย ๆ จนกระทั่งการทำซ้ำ i-th เปรียบเทียบ 2 สายของ ความยาว n / 2 (การเปรียบเทียบ n / 2) เนื่องจากจำนวนผู้ชนะจะลดลงครึ่งหนึ่งในแต่ละครั้งความสูงของแผนภูมิการเรียกซ้ำคือ log (n) ดังนั้นจึงให้อัลกอริทึม O (n log (n)) กับเรา สำหรับ n ขนาดเล็กนี่คือประมาณ O (n)

ความซับซ้อนของพื้นที่ก็คือ O (n) เช่นกันเนื่องจากในการทำซ้ำครั้งแรกเราจะต้องเก็บผู้ชนะ n / 2, ผู้ชนะซ้ำที่สอง n / 4 และอื่น ๆ (Wikipedia อ้างว่าอัลกอริทึมนี้ใช้พื้นที่คงที่ฉันไม่เข้าใจวิธี)

นี่คือการดำเนินการตามสกาล่า; อย่าลังเลที่จะแปลงเป็นภาษาโปรแกรมที่คุณชื่นชอบ

def lexicographicallyMinRotation(s: String): String = {
 @tailrec
 def duel(winners: Seq[Int]): String = {
   if (winners.size == 1) s"${s.slice(winners.head, s.length)}${s.take(winners.head)}"
   else {
     val newWinners: Seq[Int] = winners
       .sliding(2, 2)
       .map {
         case Seq(x, y) =>
           val range = y - x
           Seq(x, y)
             .map { i =>
               val segment = if (s.isDefinedAt(i + range - 1)) s.slice(i, i + range)
               else s"${s.slice(i, s.length)}${s.take(s.length - i)}"
               (i, segment)
             }
             .reduce((a, b) => if (a._2 <= b._2) a else b)
             ._1
         case xs => xs.head
       }
       .toSeq
     duel(newWinners)
   }
 }

 duel(s.indices)
}

-1

ฉันไม่เห็นอะไรดีไปกว่า O (N²)

หากคุณมีรายการของจำนวนเต็ม N คุณสามารถเลือกที่เล็กที่สุดในการเปรียบเทียบ O (N)

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

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