한 번에 모두 일치하는 항목 찾기


18

이 과제는 다음 문제를 해결하기 위해 코드를 작성하는 것입니다.

두 개의 문자열 A와 B가 주어지면 코드는 다음 속성을 사용하여 A의 하위 문자열의 시작 및 끝 인덱스를 출력해야합니다.

  • A의 하위 문자열은 문자열의 단일 문자를 최대 한 번 대체하여 B의 일부 하위 문자열과도 일치해야합니다.
  • 더 이상 첫 번째 특성을 만족시키는 A의 하위 문자열이 없어야합니다.

예를 들면 다음과 같습니다.

A = xxxappleyyyyyyy

B = zapllezzz

apple인덱스가 있는 부분 문자열 4 8(인덱싱 1)은 유효한 출력입니다.

점수

답의 점수는 코드 길이 (바이트) + 길이가 각각 백만 개의 문자열 A 및 B에서 실행될 때 컴퓨터에서 걸리는 시간 (초)입니다.

테스트 및 입력

http://hgdownload.cse.ucsc.edu/goldenPath/hg38/chromosomes/ 의 문자열에서 가져온 백만 길이의 두 문자열에서 코드를 실행합니다 .

입력은 표준 입력 상태이며 새 줄로 구분 된 두 개의 문자열입니다.

언어와 라이브러리

무료로 사용할 수있는 컴파일러 / 인터프리터 등이있는 모든 언어를 사용할 수 있습니다. Linux 및 오픈 소스이며 Linux에서 무료로 사용할 수있는 모든 라이브러리

내 컴퓨터 타이밍이 내 컴퓨터에서 실행됩니다. 이것은 AMD FX-8350 8 코어 프로세서에 표준 우분투 설치입니다. 이것은 또한 코드를 실행할 수 있어야 함을 의미합니다. 결과적으로 쉽게 구할 수있는 무료 소프트웨어 만 사용하고 코드를 컴파일하고 실행하는 방법에 대한 전체 지침을 포함하십시오.


더 많은 절대 점수 정의가 필요합니다. 컴퓨터에서 실행 시간이 좋은 점수 매기기 방법처럼 들리지 않습니다.
mbomb007

7
@ mbomb007 코드 속도를 측정 할 수있는 유일한 방법이며 PPCG에서 가장 빠른 코드 경쟁에서 항상 사용되는 방법입니다! 사람들은 보통 자신의 컴퓨터에 자신의 답변을 게시하고 OP가 최종 점수를 산출 할 때까지 기다립니다. 적어도 100 % 모호하지 않습니다.

5
@ mbomb007은 가장 빠른 코드를 위해 매우 널리 사용되는 채점 방법입니다.
Optimizer

if(hash(str1 == test1 && str2 == test2)) print("100,150") else ..-생각?
John Dvorak

2
@FryAmTheEggman 매우 드물게 동점 인 경우 첫 번째 답변이 우선합니다. appley일치하려면 두 개의 대체가 필요합니다 apllez. 어쩌면 당신은 그것이 apllB에 있고 그렇지 않은 것을 놓쳤 appl습니까?

답변:


4

C ++ 시간 : O (n ^ 2), 추가 공간 : O (1)

내 컴퓨터에서 15K 데이터를 완료하려면 0.2 초가 걸립니다.

컴파일하려면 다음을 사용하십시오.

g++ -std=c++11 -O3 code.cpp -o code

실행하려면 다음을 사용하십시오.

./code < INPUT_FILE_THAT_CONTAINS_TWO_LINES_SPERATED_BY_A_LINE_BREAK

설명

아이디어는 문자열, 간단 s1하고 s2우리가 상쇄하려고 s2으로 i:

s1: abcabcabc
s2: bcabcab

오프셋이 3 인 경우

s1: abcabcabc
s2:    bcabcab

그런 다음 각 오프셋에 대해 및 i에서 동적 프로그래밍 스캔을 수행합니다 . 각각에 대해 , 할 수 있는 최대 길이는 일 이 같은 . 마찬가지로하자 최대의 길이가 되도록 문자열 과 기껏해야 1 문자가 다르다.s1[i:]s2jf[j, 0]ds1[j - d:j] == s2[j - i - d: j - i]f[j, 1]ds1[j - d:j]s2[j - i - d:j - i]

그래서 s1[j] == s2[j - i]우리는

f[j, 0] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j]
f[j, 1] = f[j - 1, 1] + 1  // concat solution in f[j - 1, 1] and s1[j]

그렇지 않으면:

f[j, 0] = 0  // the only choice is empty string
f[j, 1] = f[j - 1, 0] + 1  // concat solution in f[j - 1, 0] and s1[j] (or s2[j - i])

과:

f[-1, 0] = f[-1, 1] = 0 

f [j, :]를 계산하기 위해 f [j-1, :] 만 필요하기 때문에 O (1) 여분의 공간 만 사용됩니다.

마지막으로 최대 길이는 다음과 같습니다.

max(f[j, 1] for all valid j and all i)

암호

#include <string>
#include <cassert>
#include <iostream>

using namespace std;

int main() {
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);
    int n1, n2;
    n1 = s1.size();
    n2 = s2.size();
    int max_len = 0;
    int max_end = -1;
    for(int i = 1 - n2; i < n1; i++) {
        int f0, f1;
        int max_len2 = 0;
        int max_end2 = -1;
        f0 = f1 = 0;
        for(int j = max(i, 0), j_end = min(n1, i + n2); j < j_end; j++) {
            if(s1[j] == s2[j - i]) {
                f0 += 1;
                f1 += 1;
            } else {
                f1 = f0 + 1;
                f0 = 0;
            }
            if(f1 > max_len2) {
                max_len2 = f1;
                max_end2 = j + 1;
            }
        }
        if(max_len2 > max_len) {
            max_len = max_len2;
            max_end = max_end2;
        }
    }
    assert(max_end != -1);
    // cout << max_len << endl;
    cout << max_end - max_len + 1 << " " << max_end << endl;
}

죄송합니다. 코드를 살펴본 결과 "apple"및 "aplle"예제와 같이 한 문자를 제외하고 문자열 일치 가능성을 고려한 방법을 찾을 수 없습니다. 설명해 주시겠습니까?
rorlork

@rcrmn 이것이 바로 동적 프로그래밍 부분입니다. 이해하기 위해서는 간단한 경우에 f [j, 0] 및 f [j, 1]을 수동으로 계산해 보는 것이 도움이됩니다. 이전 코드에는 몇 가지 버그가 있으므로 게시물을 업데이트했습니다.
Ray

감사합니다. O (n log n) 솔루션도 있다고 생각하십니까?

2

C ++

나는 이것을하기 위해 좋은 알고리즘을 생각했지만, 오늘 약간 산만하고 잘 작동하는 것을 생각할 수 없었다. 이것은 O (n ^ 3) 시간에 실행되므로 속도가 느립니다. 내가 생각한 다른 옵션은 이론적으로 더 빠를 수 있지만 O (n ^ 2) 공간을 차지했을 것이고 1M의 입력으로 인해 더 나빴을 것입니다.

부끄러운 일이지만 15K의 입력에는 190 초가 걸립니다. 나는 그것을 개선하려고 노력할 것이다. 편집 : 다중 처리가 추가되었습니다. 이제 8 개의 스레드에서 15K 입력에 37 초가 걸립니다.

#include <string>
#include <vector>
#include <sstream>
#include <chrono>
#include <thread>
#include <atomic>
#undef cin
#undef cout
#include <iostream>

using namespace std;

typedef pair<int, int> range;

int main(int argc, char ** argv)
{
    string a = "xxxappleyyyyyyy";
    string b = "zapllezzz";

    getline(cin, a);
    getline(cin, b);

    range longestA;
    range longestB;

    using namespace std::chrono;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    unsigned cores = thread::hardware_concurrency(); cores = cores > 0 ? cores : 1;

    cout << "Processing on " << cores << " cores." << endl;

    atomic<int> processedCount(0);

    vector<thread> threads;

    range* longestAs = new range[cores];
    range* longestBs = new range[cores];
    for (int t = 0; t < cores; ++t)
    {
        threads.push_back(thread([&processedCount, cores, t, &a, &b, &longestBs, &longestAs]()
        {
            int la = a.length();
            int l = la / cores + (t==cores-1? la % cores : 0);
            int lb = b.length();
            int aS = t*(la/cores);

            for (int i = aS; i < aS + l; ++i)
            {
                int count = processedCount.fetch_add(1);
                if ((count+1) * 100 / la > count * 100 / la)
                {
                    cout << (count+1) * 100 / la << "%" << endl;
                }
                for (int j = 0; j < lb; ++j)
                {
                    range currentB = make_pair(j, j);
                    bool letterChanged = false;
                    for (int k = 0; k + j < lb && k + i < la; ++k)
                    {
                        if (a[i + k] == b[j + k])
                        {
                            currentB = make_pair(j, j + k);
                        }
                        else if (!letterChanged)
                        {
                            letterChanged = true;
                            currentB = make_pair(j, j + k);
                        }
                        else
                        {
                            break;
                        }
                    }
                    if (currentB.second - currentB.first > longestBs[t].second - longestBs[t].first)
                    {
                        longestBs[t] = currentB;
                        longestAs[t] = make_pair(i, i + currentB.second - currentB.first);
                    }
                }
            }
        }));
    }

    longestA = make_pair(0,0);
    for(int t = 0; t < cores; ++t)
    {
        threads[t].join();

        if (longestAs[t].second - longestAs[t].first > longestA.second - longestA.first)
        {
            longestA = longestAs[t];
            longestB = longestBs[t];
        }
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);

    cout << "First substring at range (" << longestA.first << ", " << longestA.second << "):" << endl;
    cout << a.substr(longestA.first, longestA.second - longestA.first + 1) << endl;
    cout << "Second substring at range (" << longestB.first << ", " << longestB.second << "):" << endl;
    cout << b.substr(longestB.first, longestB.second - longestB.first + 1) << endl;
    cout << "It took me " << time_span.count() << " seconds for input lengths " << a.length() << " and " << b.length() <<"." << endl;

    char c;
    cin >> c;
    return 0;
}

정말 나쁜 해결책이어서 정말 죄송합니다. 더 나은 시간에 이것을 달성하기 위해 알고리즘을 찾고 있었지만 지금은 아무것도 찾지 못했습니다 ...
rorlork

글쎄, 필요한 작업의 복잡도는 O (n ^ 4)에서 O (n ^ 5) 정도
여야

나는 그것이 최악의 경우에는 O (n ^ 3)와 같아야한다고 생각합니다. 적어도 내 알고리즘으로는 그렇지 않습니다. 어쨌든, 어떤 종류의 트리 검색과 같이 그것을 개선하기 위해 무언가를 할 수 있다고 확신하지만 그것이 어떻게 구현되는지는 확실하지 않습니다.
rorlork

오 예, O (n ^ 3) 그것은 ... O (n ^ 4)를 취한 다른 접근 방식을 염두에두고 있었지만, 지금은 xD에 의해 다소 쓸모가 없습니다
hoffmale

당신이에서 루프에 대한 두 개의 외부에서 수표를 변경하는 경우 당신은 시간의 작은 금액을 절약 할 수 i < a.length()i < a.length - (longestA.second - longestA.first)당신이 현재 가장 긴 하나보다 일치 작은 처리 할 필요가 없기 때문 (J 및 b.length를 ()에 대한 동일)
hoffmale

2

아르 자형

이전 솔루션으로 문제를 복잡하게 만드는 것 같습니다. 이것은 이전보다 약 50 % 빠르며 (15k 스트링에서 23 초) 매우 간단합니다.

rm(list=ls(all=TRUE))
a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
matchLen=1
matchIndex=1
indexA = 1
repeat {    
    i = 0
    repeat {
        srch = substring(a,indexA,indexA+matchLen+i)
        if (agrepl(srch,b,max.distance=list(insertions=0,deletions=0,substitutions=1)))
            i = i + 1
        else {
            if (i > 0) {
                matchLen = matchLen + i - 1
                matchIndex = indexA
            }
            break
        }
    }
    indexA=indexA+1
    if (indexA + matchLen > nchar(a)) break
}
c(matchIndex, matchLen + matchIndex)
print (substring(a,matchIndex, matchLen + matchIndex))
print(proc.time()-s)

이것은 언어로 인해 경쟁자가 될 수 없지만, 나는 약간 재미있었습니다.
복잡성에 대해서는 확신 할 수 없지만 ~ 15k 문자열에서 단일 스레드를 사용하면 43 초가 걸립니다. 그것의 가장 큰 부분은 배열의 정렬이었습니다. 다른 라이브러리를 사용해 보았지만 크게 개선되지 않았습니다.

a="xxxappleyyyyyyy"
b="zapllezzz"
s=proc.time()
N=nchar
S=substring
U=unlist
V=strsplit
A=N(a)
B=N(b)
a=S(a,1:A)
b=S(b,1:B)
a=sort(a,method="quick")
b=sort(b,method="quick")
print(proc.time()-s)
C=D=1
E=X=Y=I=0
repeat{
    if(N(a[C])>E && N(b[D])>E){
        for(i in E:min(N(a[C]),N(b[D]))){
            if (sum(U(V(S(a[C],1,i),''))==U(V(S(b[D],1,i),'')))>i-2){
                F=i
            } else break
        }
        if (F>E) {
            X=A-N(a[C])+1
            Y=X+F-1
            E=F
        }
        if (a[C]<b[D])
            C=C+1
            else
            D=D+1
    } else
        if(S(a[C],1,1)<S(b[D],1,1))C=C+1 else D=D+1
    if(C>A||D>B)break
}
c(X,Y)
print(proc.time()-s)

방법:

  • 각 문자열에 대한 접미사 배열 만들기
  • 접미사 배열 순서
  • 각 배열의 시작 부분을 비교하는 엇갈린 방식으로 각 배열을 단계별로 살펴보십시오.

물론 R에서 가장 쉬운 해결책은 Bioconductor를 사용하는 것입니다.
archaephyrryx

@archaephyrryx 바이오 컨덕터 솔루션은 재미있을 것입니다.

그것은 ...하지만 문서에 대한 나의 빠른 읽기는 내 머리 위로 갔다. 어쩌면 내가 용어를 이해한다면 :-)
MickyT

첫 번째 댓글을 삭제했습니다. 물론이 과제에 대해 원하는 오픈 소스 라이브러리를 사용할 수 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.