Chuỗi con dài nhất trong thời gian tuyến tính


45

Thách thức này là về viết mã để giải quyết vấn đề sau.

Cho hai chuỗi A và B, mã của bạn sẽ xuất ra các chỉ số bắt đầu và kết thúc của một chuỗi con của A với các thuộc tính sau.

  • Chuỗi con của A cũng phải khớp với một số chuỗi con của B.
  • Không nên có chuỗi con của A thỏa mãn thuộc tính đầu tiên.

Ví dụ:

A = xxxappleyyyyyyy

B = zapplezzz

Chuỗi con applecó chỉ số 4 8(lập chỉ mục từ 1) sẽ là đầu ra hợp lệ.

Chức năng

Bạn có thể giả sử đầu vào sẽ đạt tiêu chuẩn trong hoặc trong một tệp trong thư mục cục bộ, đó là lựa chọn của bạn. Định dạng tệp sẽ chỉ đơn giản là hai chuỗi, cách nhau bởi một dòng mới. Câu trả lời phải là một chương trình đầy đủ và không chỉ là một chức năng.

Cuối cùng tôi muốn kiểm tra mã của bạn trên hai chuỗi được lấy từ các chuỗi trong http: // hgdoad.cse.ucsc.edu/goldenPath/hg38/chromosomes/ .

Ghi bàn

Đây là mã golf với một twist. Mã của bạn phải được chạy O(n)đúng lúc, trong đó ntổng chiều dài của đầu vào.

Ngôn ngữ và thư viện

Bạn có thể sử dụng bất kỳ ngôn ngữ nào có trình biên dịch / trình thông dịch / có sẵn miễn phí. cho Linux. Bạn chỉ nên sử dụng các thư viện mã nguồn mở tiêu chuẩn không được thiết kế để giải quyết nhiệm vụ này. Trong trường hợp có tranh chấp, tôi sẽ tính đây là bất kỳ thư viện nào đạt tiêu chuẩn với ngôn ngữ của bạn hoặc thư viện mà bạn có thể cài đặt trong máy ubfox mặc định từ kho lưu trữ mặc định.

Thông tin hữu ích

Có ít nhất hai cách để giải quyết vấn đề này trong thời gian tuyến tính. Một là trước tiên tính toán cây hậu tố và thứ hai là trước tiên tính toán mảng hậu tố và mảng LCP.


4
O(n) timeBạn có chắc là nó có thể?
Savenkov Alexey

17
@Lembik Xin lỗi, nhưng đây là những thuật toán rất phức tạp và thật không vui khi nhận được hơn 100 dòng mã.
FUZxxl 3/03/2015

4
Bài viết tại liên kết thứ hai mà bạn cung cấp trong "Thông tin hữu ích" nói rằng "việc xây dựng [một cây hậu tố] cần có thời gian O (N ^ 2)"
KSFT

3
@Lembik Bạn chỉ nên đặt câu hỏi [mã nhanh nhất], trong đó chương trình có trường hợp xấu nhất tốt nhất trong ký hiệu big-oh thắng. Sau đó, ít nhất bạn sẽ nhận được một số câu trả lời, và thậm chí ai đó có thể giải quyết nó trong O (n), họ sẽ thắng.
mbomb007

9
Đây phải là câu hỏi có câu trả lời bị xóa nhiều nhất cho mỗi câu trả lời hợp lệ ...
FlipTack

Câu trả lời:


39

Python 2, 646 byte

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

Điều này sử dụng thuật toán nghiêng được mô tả trong "Xây dựng mảng tuyến tính đơn giản" của Kärkkäinen và Sanders. Việc triển khai C ++ bao gồm trong bài báo đó đã cảm thấy hơi "khó chơi", nhưng vẫn còn nhiều chỗ để làm cho nó ngắn hơn. Ví dụ, chúng ta có thể lặp lại cho đến khi đến một mảng có độ dài một, thay vì ngắn mạch như trong bài báo, mà không vi phạm O(n)yêu cầu.

Đối với phần LCP, tôi đã theo dõi "Tính toán tiền tố dài nhất theo thời gian tuyến tính trong các mảng Suffix và các ứng dụng của nó" của Kusai et al.

Chương trình xuất ra 1 0nếu chuỗi con chung dài nhất trống.

Dưới đây là một số mã phát triển bao gồm một phiên bản trước đó của chương trình theo sau triển khai C ++ hơn một chút, một số cách tiếp cận chậm hơn để so sánh và trình tạo trường hợp thử nghiệm đơn giản:

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
Chỉnh sửa cho tôi nếu tôi sai, nhưng đây không thực sự là 739 byte? Tôi đã sao chép vào Mothereff.in/byte-count và xóa 2 khoảng trắng khỏi dòng 6-9, nhưng tôi không chắc liệu điều đó có đúng không.
Patrick Roberts

2
@PatrickRoberts Đó là các tab.
Mitch Schwartz

2
Câu trả lời tốt đẹp! Bạn có thể muốn xem GSACA một SACA thời gian tuyến tính mới từ năm 2016. Thực hiện tham chiếu là 246 dòng đầy ý kiến ​​(170 không có ý kiến) và có vẻ rất golf. Bạn sẽ tìm thấy nó trên github.
Christoph

1
@MitchSchwartz Tôi hiện đang cố gắng duy trì noPMO, vì vậy tôi không thể cảm nhận được cảm xúc mạnh mẽ ngay bây giờ (có lẽ là do hóa chất não mất cân bằng). Vào thời điểm đọc mã nhanh chóng, động cơ chơi gôn cú pháp của tôi phát hiện ra điều đó và tôi không nhớ cảm giác cụ thể nào. Bạn đã nghĩ về điều tương tự hoặc tại sao câu hỏi? :) Bây giờ tôi tò mò.
Yytsi

1
@TuukkaX Đó là một phản ứng thú vị mà tôi không mong đợi. Chà, tôi không chắc là tôi có nên nói điều này theo một cách đặc biệt nào đó không, nhưng thực tế là nhận xét ban đầu của bạn không thực sự đúng đã đóng một phần lý do tại sao tôi quyết định hỏi. :)
Mitch Schwartz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.