ให้สองลำดับค้นหาการทับซ้อนสูงสุดระหว่างสิ้นสุดของหนึ่งและจุดเริ่มต้นของอื่น ๆ


11

ฉันต้องการค้นหารหัส (หลอก) ที่มีประสิทธิภาพเพื่อแก้ไขปัญหาต่อไปนี้:

ได้รับลำดับสองของ (ไม่จำเป็นต้องแตกต่างกัน) จำนวนเต็ม(a[1], a[2], ..., a[n])และ(b[1], b[2], ..., b[n])ค้นหาสูงสุดdดังกล่าวว่าa[n-d+1] == b[1], a[n-d+2] == b[2]... a[n] == b[d]และ

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


หากสามารถคำนวณแฮชแบบกลิ้งสำหรับกลุ่มวัตถุในอาร์เรย์ของคุณฉันคิดว่าสิ่งนี้สามารถทำได้อย่างมีประสิทธิภาพมากขึ้น คำนวณแฮชสำหรับองค์ประกอบb[1] to b[d]แล้วไปที่อาร์เรย์aคำนวณแฮชสำหรับa[1] to a[d]สิ่งที่ตรงกันนั่นคือคำตอบของคุณถ้าไม่คำนวณแฮชa[2] to a[d+1]โดยใช้การแฮชที่คำนวณa[1] to a[d]ใหม่ แต่ฉันไม่ทราบว่าวัตถุในอาเรย์นั้นตอบสนองได้หรือไม่สำหรับแฮชที่จะถูกคำนวณกับพวกมัน
SomeDude

2
@ เบ็บฉันขอโทษฉันคิดว่าในที่สุดฉันก็เข้าใจสิ่งที่คุณพยายามที่จะบรรลุ ซึ่งก็คือการหาสิ่งที่ทับซ้อนกันสูงสุดระหว่างจุดสิ้นสุดของการมีจุดเริ่มต้นของ a เช่นนี้ b
user3386109

1
ดูเหมือนว่าผมว่าปัญหาที่เกิดขึ้นเป็นรูปแบบในการจับคู่สายซึ่งสามารถแก้ไขได้ด้วยรูปแบบที่หลากหลายเป็นอัลกอริทึม Knuth มอร์ริสแพรตต์ เวลาทำงานจะเป็น O (m + n) ซึ่งmเป็นจำนวนขององค์ประกอบในaและเป็นจำนวนขององค์ประกอบในn bน่าเสียดายที่ฉันไม่มีประสบการณ์เพียงพอกับ KMP เพื่อบอกวิธีปรับใช้
user3386109

1
@ user3386109 โซลูชันของฉันเป็นรูปแบบของอัลกอริทึมการจับคู่สตริงที่เรียกว่าRabin-Karpโดยใช้วิธีของ Hornerเป็นฟังก์ชันแฮช
แดเนียล

1
@Daniel Ah, ฉันรู้ว่าฉันเคยเห็นแฮชกลิ้งใช้อยู่ที่ไหนสักแห่ง แต่จำไม่ได้ว่าอยู่ที่ไหน :)
user3386109

คำตอบ:


5

คุณสามารถใช้อัลกอริทึม z อัลกอริธึมเชิงเส้น ( O (n) ) ที่:

รับสตริงSของความยาว n อัลกอริทึม Z สร้างอาร์เรย์Z โดยที่Z [i]คือความยาวของสตริงย่อยที่ยาวที่สุดเริ่มต้นจากS [i] ซึ่งเป็นส่วนนำหน้าของS

คุณจำเป็นต้องเชื่อมอาร์เรย์ของคุณ ( B + ) และเรียกใช้อัลกอริทึมในอาร์เรย์สร้างที่เกิดจนถึงคนแรกที่ฉันดังกล่าวว่าZ [ผม] + ฉัน == เมตร + n

ตัวอย่างเช่นสำหรับa = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0] การต่อข้อมูลจะเป็น [2, 3, 6, 2, 1 , 0, 1, 2, 3, 6, 2, 3] ซึ่งจะให้ผลผลิตZ [10] = 2 ตอบสนองZ [ผม] + ฉัน = 12 = M + n


สวย! ขอบคุณ
becko

3

สำหรับความซับซ้อนของเวลา / พื้นที่ O (n) เคล็ดลับคือการประเมินค่าแฮชสำหรับแต่ละลำดับ พิจารณาอาร์เรย์b:

[b1 b2 b3 ... bn]

ด้วยวิธีการของ Hornerคุณสามารถประเมินค่าแฮชที่เป็นไปได้ทั้งหมดสำหรับแต่ละลำดับ เลือกค่าฐานB(ใหญ่กว่าค่าใด ๆ ในทั้งสองอาร์เรย์ของคุณ):

from b1 to b1 = b1 * B^1
from b1 to b2 = b1 * B^1 + b2 * B^2
from b1 to b3 = b1 * B^1 + b2 * B^2 + b3 * B^3
...
from b1 to bn = b1 * B^1 + b2 * B^2 + b3 * B^3 + ... + bn * B^n

โปรดทราบว่าคุณสามารถประเมินแต่ละลำดับในเวลา O (1) โดยใช้ผลลัพธ์ของลำดับก่อนหน้าดังนั้นงานทั้งหมด O (n)

ตอนนี้คุณมีอาร์เรย์Hb = [h(b1), h(b2), ... , h(bn)]ที่Hb[i]เป็นกัญชาจากจนกว่าb1bi

ทำสิ่งเดียวกันสำหรับอาร์เรย์aแต่ด้วยเล่ห์เหลี่ยมเล็กน้อย:

from an to an   =  (an   * B^1)
from an-1 to an =  (an-1 * B^1) + (an * B^2)
from an-2 to an =  (an-2 * B^1) + (an-1 * B^2) + (an * B^3)
...
from a1 to an   =  (a1   * B^1) + (a2 * B^2)   + (a3 * B^3) + ... + (an * B^n)

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

from an to an =    (an   * B^1)

for the next sequence, multiply the previous by B: (an * B^1) * B = (an * B^2)
now sum with the new value multiplied by B: (an-1 * B^1) + (an * B^2) 
hence:

from an-1 to an =  (an-1 * B^1) + (an * B^2)

ตอนนี้คุณมีอาร์เรย์Ha = [h(an), h(an-1), ... , h(a1)]ที่Ha[i]เป็นกัญชาจากจนกว่าaian

ทีนี้คุณสามารถเปรียบเทียบค่าHa[d] == Hb[d]ทั้งหมดdจาก n เป็น 1 หากตรงกับที่คุณมีคำตอบ


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

ซึ่งหมายความว่าทั้งสองลำดับที่แตกต่างกันอาจมีกัญชาเดียวกัน แต่สองลำดับที่เท่ากันจะเสมอมีกัญชาเดียวกัน


คุณช่วยกรุณาเริ่มคำตอบนี้ด้วยการประเมินความต้องการทรัพยากรได้หรือไม่?
greybeard

2

สิ่งนี้สามารถทำได้ในเวลาเชิงเส้น, O (n)และO (n)พื้นที่พิเศษ ฉันจะถือว่าอาร์เรย์อินพุตเป็นสตริงอักขระ แต่นี่ไม่จำเป็น

วิธีการที่ไร้เดียงสาจะ - หลังจากจับคู่อักขระkที่เท่ากัน - ค้นหาอักขระที่ไม่ตรงกันและย้อนกลับหน่วยk-1ในaรีเซ็ตดัชนีในbแล้วเริ่มกระบวนการจับคู่จากตรงนั้น นี่แสดงให้เห็นอย่างชัดเจนว่าเป็นกรณีที่เลวร้ายที่สุดO (n²)

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

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

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

index: 0 1 2 3 4 5 6 7 8
b:     a c a a c a a c d
ref:   0 0 0 1 0 0 1 0 5

ตัวอย่างเช่นถ้าเรามีเท่ากับ "acaacaaca" ไม่ตรงกันเป็นครั้งแรกที่เกิดขึ้นในตัวละครสุดท้าย ข้อมูลข้างต้นบอกให้อัลกอริทึมกลับไปเป็นดัชนีb 5 เนื่องจาก "acaac" เป็นเรื่องปกติ และจากนั้นก็มีเพียงการเปลี่ยนแปลงดัชนีปัจจุบันในเราสามารถดำเนินการจับคู่ที่ดัชนีปัจจุบันของ ในตัวอย่างนี้การจับคู่ของอักขระสุดท้ายจะสำเร็จ

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

นี่คือการดำเนินการตามแนวคิดดังกล่าวใน JavaScript โดยใช้ไวยากรณ์พื้นฐานที่สุดของภาษานั้นเท่านั้น:

function overlapCount(a, b) {
    // Deal with cases where the strings differ in length
    let startA = 0;
    if (a.length > b.length) startA = a.length - b.length;
    let endB = b.length;
    if (a.length < b.length) endB = a.length;
    // Create a back-reference for each index
    //   that should be followed in case of a mismatch.
    //   We only need B to make these references:
    let map = Array(endB);
    let k = 0; // Index that lags behind j
    map[0] = 0;
    for (let j = 1; j < endB; j++) {
        if (b[j] == b[k]) {
            map[j] = map[k]; // skip over the same character (optional optimisation)
        } else {
            map[j] = k;
        }
        while (k > 0 && b[j] != b[k]) k = map[k]; 
        if (b[j] == b[k]) k++;
    }
    // Phase 2: use these references while iterating over A
    k = 0;
    for (let i = startA; i < a.length; i++) {
        while (k > 0 && a[i] != b[k]) k = map[k];
        if (a[i] == b[k]) k++;
    }
    return k;
}

console.log(overlapCount("ababaaaabaabab", "abaababaaz")); // 7

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

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

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