สตริงย่อยทั่วไปที่ยาวที่สุดในเวลาเชิงเส้น


45

ความท้าทายนี้เกี่ยวกับการเขียนรหัสเพื่อแก้ไขปัญหาต่อไปนี้

รับสองสาย A และ B รหัสของคุณควรส่งออกดัชนีเริ่มต้นและจุดสิ้นสุดของสตริงย่อยของ A ที่มีคุณสมบัติดังต่อไปนี้

  • สตริงย่อยของ A ควรตรงกับสตริงย่อยบางส่วนของ B ด้วย
  • ไม่ควรมีสตริงย่อยของ A ที่สอดคล้องกับคุณสมบัติแรกอีกต่อไป

ตัวอย่างเช่น:

A = xxxappleyyyyyyy

B = zapplezzz

สตริงย่อยappleพร้อมดัชนี4 8(การทำดัชนีจาก 1) จะเป็นเอาต์พุตที่ถูกต้อง

ฟังก์ชั่น

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

ในที่สุดผมต้องการทดสอบรหัสของคุณทั้งสองที่นำมาจากสตริงสตริงในhttp://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/

คะแนน

นี่คือรหัสกอล์ฟที่มีเกลียว รหัสของคุณจะต้องทำงานในO(n)เวลาซึ่งnเป็นความยาวรวมของการป้อนข้อมูล

ภาษาและห้องสมุด

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

ข้อมูลที่เป็นประโยชน์

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

  • นี่คือคำอธิบายแบบเต็มและ (อาจมากกว่า -) รายละเอียดของการสร้างต้นไม้ต่อท้ายเชิงเส้นเวลา นอกจากนี้ยังมีคำตอบให้ดีมากเกี่ยวกับเส้นเวลาก่อสร้างต้นไม้ต่อท้ายที่https://stackoverflow.com/questions/9452701/ukkonens-suffix-tree-algorithm-in-plain-english มันมีลิงค์ไปยังซอร์สโค้ดด้วย สามารถอ่านคำอธิบายโดยละเอียดได้ที่นี่คราวนี้เป็นการแก้ปัญหาอย่างเต็มรูปแบบใน C
  • ส่วนที่ 2 ของhttp://www.cs.cmu.edu/~guyb/realworld/papersS04/KaSa03.pdfให้อัลกอริธึมการสร้างอาร์เรย์ส่วนต่อท้ายเชิงเส้นตรงเวลาและภาคผนวก A มีซอร์สโค้ด C ++ คำตอบนี้จะบอกคุณว่าแล้วการคำนวณย่อยทั่วไปที่ยาวที่สุดhttps://cs.stackexchange.com/questions/9555/computing-the-longest-common-substring-of-two-strings-using-suffix-arrays หมวดที่ 5 ของhttps://courses.csail.mit.edu/6.851/spring12/scribe/lec16.pdfซึ่งมีวิดีโอบรรยายที่เกี่ยวข้องhttps://courses.csail.mit.edu/6.851/spring12/lectures/L16 .htmlยังอธิบายถึงอัลกอริทึมเดียวกันโดยเริ่มต้นที่ 1:16:00

4
O(n) timeคุณแน่ใจหรือว่าเป็นไปได้
Savenkov Alexey

17
@ Lembik ขออภัย แต่สิ่งเหล่านี้เป็นอัลกอริธึมที่ซับซ้อนมากและมันก็ไม่สนุกเลยที่จะรวบรวมโค้ดมากกว่า 100 บรรทัด
FUZxxl

4
บทความในลิงค์ที่สองที่คุณให้ไว้ภายใต้ "ข้อมูลที่เป็นประโยชน์" กล่าวว่า "การสร้าง [ต้นไม้ต่อท้าย] ต้องใช้เวลา O (N ^ 2) เวลา"
KSFT

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

9
นี่จะเป็นคำถามที่มีคำตอบที่ถูกลบมากที่สุดต่อคำตอบที่ถูกต้อง ...
FlipTack

คำตอบ:


39

Python 2, 646 ไบต์

G=range;w=raw_input;z=L,m,h=[0]*3
s=w();t=len(s);s+='!%s#'%w();u=len(s);I=z*u
def f(s,n):
 def r(o):
    b=[[]for _ in s];c=[]
    for x in B[:N]:b[s[x+o]]+=x,
    map(c.extend,b);B[:N]=c
 M=N=n--~n/3;t=n%3%2;B=G(n+t);del B[::3];r(2);u=m=p=r(1)>r(0);N-=n/3
 for x in B*1:v=s[x:x+3];m+=u<v;u=v;B[x/3+x%3/2*N]=m
 A=1/M*z or f(B+z,M)+z;B=[x*3for x in A if x<N];J=I[r(0):n];C=G(n)
 for k in C:b=A[t]/N;a=i,j=A[t]%N*3-~b,B[p];q=p<N<(s[i:i-~b],J[i/3+b+N-b*N])>(s[j+t/M:j-~b],J[j/3+b*N]);C[k]=x=a[q];I[x]=k;p+=q;t+=1-q
 return C
S=f(map(ord,s)+z*40,u)
for i in G(u):
 h-=h>0;j=S[I[i]-1]
 while s[i+h]==s[j+h]:h+=1
 if(i<t)==(t<j)<=h>m:m=h;L=min(i,j)
print-~L,L+m

สิ่งนี้ใช้อัลกอริธึมเอียงที่อธิบายไว้ใน "Simple Linear Work Suffix Array Construction" โดยKärkkäinenและ Sanders การใช้งาน C ++ ที่รวมอยู่ในกระดาษนั้นให้ความรู้สึก "กอล์ฟ" น้อย แต่ก็มีพื้นที่เหลือเฟือสำหรับการทำให้สั้นลง ตัวอย่างเช่นเราสามารถเรียกคืนเงินได้จนกว่าจะถึงอาร์เรย์ที่มีความยาวหนึ่งเส้นแทนที่จะเป็นการลัดวงจรเช่นเดียวกับในกระดาษโดยไม่ละเมิดO(n)ข้อกำหนด

สำหรับส่วน LCP ฉันติดตาม "การคำนวณเชิงเส้นยาวที่สุดร่วมกันของคำนำหน้าในคำต่อท้ายอาร์เรย์และแอปพลิเคชัน" โดย Kusai et al.

โปรแกรมจะแสดงผล1 0ถ้าสตริงย่อยทั่วไปที่ยาวที่สุดว่างเปล่า

นี่คือรหัสการพัฒนาบางส่วนที่รวมถึงโปรแกรมรุ่นก่อนหน้าซึ่งตามหลังการใช้งาน C ++ อย่างใกล้ชิดมากขึ้นวิธีการเปรียบเทียบที่ช้ากว่าและตัวสร้างกรณีทดสอบอย่างง่าย:

from random import *

def brute(a,b):
    L=R=m=0

    for i in range(len(a)):
        for j in range(i+m+1,len(a)+1):
            if a[i:j] in b:
                m=j-i
                L,R=i,j

    return L+1,R

def suffix_array_slow(s):
    S=[]
    for i in range(len(s)):
        S+=[(s[i:],i)]
    S.sort()
    return [x[1] for x in S]

def slow1(a,b):
    # slow suffix array, slow lcp

    s=a+'!'+b
    S=suffix_array_slow(s)

    L=R=m=0

    for i in range(1,len(S)):
        x=S[i-1]
        y=S[i]
        p=s[x:]+'#'
        q=s[y:]+'$'
        h=0
        while p[h]==q[h]:
            h+=1
        if h>m and len(a)==sorted([x,y,len(a)])[1]:
            m=h
            L=min(x,y)
            R=L+h

    return L+1,R

def verify(a,b,L,R):
    if L<1 or R>len(a) or a[L-1:R] not in b:
        return 0
    LL,RR=brute(a,b)
    return R-L==RR-LL

def rand_string():
    if randint(0,1):
        n=randint(0,8)
    else:
        n=randint(0,24)
    a='zyxwvutsrq'[:randint(1,10)]
    s=''
    for _ in range(n):
        s+=choice(a)
    return s

def stress_test(f):
    numtrials=2000
    for trial in range(numtrials):
        a=rand_string()
        b=rand_string()
        L,R=f(a,b)
        if not verify(a,b,L,R):
            LL,RR=brute(a,b)
            print 'failed on',(a,b)
            print 'expected:',LL,RR
            print 'actual:',L,R
            return
    print 'ok'

def slow2(a,b):
    # slow suffix array, linear lcp

    s=a+'!'+b+'#'
    S=suffix_array_slow(s)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

def suffix_array(s,K):
    # skew algorithm

    n=len(s)
    s+=[0]*3
    n0=(n+2)/3
    n1=(n+1)/3
    n2=n/3
    n02=n0+n2
    adj=n0-n1

    def radix_pass(a,o,n=n02):
        c=[0]*(K+3)
        for x in a[:n]:
            c[s[x+o]+1]+=1
        for i in range(K+3):
            c[i]+=c[i-1]
        for x in a[:n]:
            j=s[x+o]
            a[c[j]]=x
            c[j]+=1

    A=[x for x in range(n+adj) if x%3]+[0]*3

    radix_pass(A,2)
    radix_pass(A,1)
    radix_pass(A,0)

    B=[0]*n02
    t=m=0

    for x in A[:n02]:
        u=s[x:x+3]
        m+=t<u
        t=u
        B[x/3+x%3/2*n0]=m

    A[:n02]=1/n02*[0]or suffix_array(B,m)
    I=A*1
    for i in range(n02):
        I[A[i]]=i+1

    B=[3*x for x in A if x<n0]
    radix_pass(B,0,n0)

    R=[]

    p=0
    t=adj
    while t<n02:
        x=A[t]
        b=x>=n0
        i=(x-b*n0)*3-~b
        j=B[p]
        if p==n0 or ((s[i:i+2],I[A[t]-n0+1])<(s[j:j+2],I[j/3+n0]) if b else (s[i],I[A[t]+n0])<(s[j],I[j/3])):R+=i,;t+=1
        else:R+=j,;p+=1

    return R+B[p:n0]

def solve(a,b):
    # linear

    s=a+'!'+b+'#'
    S=suffix_array(map(ord,s),128)

    I=S*1
    for i in range(len(S)):
        I[S[i]]=i

    L=R=m=h=0

    for i in range(len(S)):
        if I[i]:
            j=S[I[i]-1]
            while s[i+h]==s[j+h]:
                h+=1
            if h>m and len(a)==sorted([i,j,len(a)])[1]:
                m=h
                L=min(i,j)
                R=L+h
            h-=h>0

    return L+1,R

stress_test(solve)

1
แก้ไขให้ถูกต้องถ้าฉันผิด แต่อันนี้ไม่ใช่ 739 ไบต์จริงหรือ ฉันคัดลอกไปยังmothereff.in/byte-counterและลบ 2 ช่องว่างออกจากบรรทัด 6-9 แต่ฉันไม่แน่ใจว่าถูกต้องหรือไม่
Patrick Roberts

2
@PatrickRoberts เหล่านั้นเป็นแท็บ
Mitch Schwartz

2
คำตอบที่ดี! คุณอาจต้องการดู GSACA นวนิยายเชิงเส้นเวลา SACA จากปี 2559 การอ้างอิงมี 246 บรรทัดที่เต็มไปด้วยความคิดเห็น (170 ไม่มีความคิดเห็น) และดูเหมือนว่าเล่นได้มาก คุณจะพบมันบน GitHub
Christoph

1
@MitchSchwartz ฉันกำลังพยายามอยู่ใน noPMO ดังนั้นฉันไม่สามารถรู้สึกอารมณ์รุนแรงในขณะนี้ (อาจเกิดจากสารเคมีในสมองที่ไม่สมดุล) ในช่วงเวลาที่อ่านรหัสอย่างรวดเร็วมอเตอร์ซินแทกซ์ของฉันเห็นสิ่งนั้นและฉันจำไม่ได้ว่ารู้สึกถึงอารมณ์ที่เฉพาะเจาะจง คุณคิดในสิ่งเดียวกันหรือทำไมคำถาม :) ตอนนี้ฉันอยากรู้
Yytsi

1
@TuukkaX นั่นเป็นคำตอบที่น่าสนใจที่ฉันไม่ได้คาดหวัง ดีฉันไม่แน่ใจว่าฉันควรจะใช้ถ้อยคำนี้ด้วยวิธีพิเศษบางอย่างหรือไม่ แต่ความจริงที่ว่าความคิดเห็นดั้งเดิมของคุณไม่ถูกต้องจริง ๆ แล้วมีบางส่วนที่ทำให้ฉันตัดสินใจถาม :)
Mitch Schwartz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.