KMP 알고리즘과 문자열 검색 패턴 매칭 기법 종합 정리

문자열 접두사-접미사 매칭 문제 개요

여러 문자열의 접두사와 접미사를 매칭하는 문제는 일반적으로 전처리 과정을 통해 해결한다.
고정된 단어 개수를 가진 문자열 배열 s[n]이 주어졌을 때, 각 문자열을 cin으로 입력받아 처리한다.

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 여러 문자열 매칭을 위한 전처리 연산
    }
}

답을 구할 때는 새로 생성된 string의 크기에서 최댓값을 취하며, int 변환이 필요하다.
ans = max(ans, (int)string.size());

KMP 알고리즘 구현

#include <iostream>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;

    // ne[j]는 길이가 j인 문자열의 접두사와 접미사가 일치하는 최대 길이를 저장
    // j 자체는 길이를 의미하며, j+1은 비교할 현재 인덱스를 나타냄
    for (int i = 2, j = 0; i <= n; i++)
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;
    }

    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j && s[i] != p[j + 1]) j = ne[j];
        if (s[i] == p[j + 1]) j++;
        if (j == n)
        {
            printf("%d ", i - n);
            j = ne[j];
        }
    }

    return 0;
}

특정 문자 조건을 만족하는 부분 문자열 개수 세기

'P' 문자를 만나면 문자열을 분할하고, 각 분할된 문자열에서 k개 이상의 'R'을 포함하는 부분 문자열 개수를 센다.
고정된 오른쪽 끝점에서 왼쪽 끝점을 이동시키며 조건을 만족하는 경우를 카운트한다.

#include<bits/stdc++.h>
using namespace std;
int n,k;
string s;
long long res=0;

void gao(string t){
    int i,j=0,temp=0;
    for(i=0;i<t.length();i++){
        temp+=t[i]=='R';
        while(j<=i&&temp==k){
            temp-=t[j++]=='R';
        }
        res+=j;
    }
}

int main(){
    int i;
    cin>>n>>k>>s;
    string t="";
    for(i=0;i<n;i++){
        if(s[i]=='P') gao(t), t="";
        else t+=s[i];
    }
    gao(t);
    cout<<res;
}

0부터 시작하는 KMP 구현 (j=-1 초기화 불필요)

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int ne[N];
int n, m;
string p, s;

int main(){
    cin >> n >> p >> m >> s;

    for(int i = 1, j = 0; i < n; i++){
        while(j && p[i] != p[j]) j = ne[j - 1];
        if(p[i] == p[j]) j++;
        ne[i] = j;
    }

    for(int i = 0, j = 0; i < m; i++){
        while(j && s[i] != p[j]) j = ne[j - 1];
        if(s[i] == p[j]){
            j++;
            if(j == n){
                cout << i - n + 1 << " ";
                j = ne[n - 1];
            }
        }
    }
    return 0;
}

트라이(Trie) 자료구조

트라이의 핵심은 idx 변수를 활용한 노드 관리이다.

#include <iostream>
using namespace std;

const int N = 100010;
int son[N][26], cnt[N], idx;
char str[N];

void insert(char *str){
    int p = 0;
    for (int i = 0; str[i]; i++){
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
    cnt[p]++;
}

int query(char *str){
    int p = 0;
    for (int i = 0; str[i]; i++){
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

int main(){
    int n;
    scanf("%d", &n);
    while (n--){
        char op[2];
        scanf("%s%s", op, str);
        if (*op == 'I') insert(str);
        else printf("%d\n", query(str));
    }
    return 0;
}

최대 XOR 쌍 찾기

각 숫자에 대해 최상위 비트부터 저장하여, 반대 비트를 가진 숫자를 찾아 XOR 값이 최대가 되도록 한다.

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010, M = 3100010;
int n;
int a[N], son[M][2], idx;

void insert(int x){
    int p = 0;
    for (int i = 30; i >= 0; i--){
        int &s = son[p][x >> i & 1];
        if (!s) s = ++idx;
        p = s;
    }
}

int search(int x){
    int p = 0, res = 0;
    for (int i = 30; i >= 0; i--){
        int s = x >> i & 1;
        if (son[p][!s]){
            res += 1 << i;
            p = son[p][!s];
        }
        else p = son[p][s];
    }
    return res;
}

int main(){
    scanf("%d", &n);
    for (int i = 0; i < n; i++){
        scanf("%d", &a[i]);
        insert(a[i]);
    }
    int res = 0;
    for (int i = 0; i < n; i++) res = max(res, search(a[i]));
    printf("%d\n", res);
    return 0;
}

순열 사이클 찾기 (교환 병 문제)

int main(){
    cin>>n;
    int cnt=0;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++){
        if(!st[i]){
            for (int j = i; !st[j]; j=a[j]){
                st[j]=true;
            }
            cnt++;
        }
    }
    cout<<n-cnt<<endl;
    return 0;
}

부분 문자열 등장 횟수 (접두사 합 활용)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1010;
int n, m, Q;
string S, T;
int s[N];

int main(){
    scanf("%d%d%d", &n, &m, &Q);
    char strs[N], strt[N];
    scanf("%s%s", strs, strt);
    S = strs, T = strt;
    S = ' ' + S;

    for (int i = m; i <= n; i++){
        s[i] = s[i-1];
        if (S.substr(i - (m-1), m) == T)
            s[i]++;
    }

    while (Q--){
        int l, r;
        scanf("%d%d", &l, &r);
        l += (m - 1);
        if (l > r) puts("0");
        else printf("%d\n", s[r] - s[l-1]);
    }
    return 0;
}

양방향 BFS를 활용한 문자열 변환

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
#include <queue>
using namespace std;

const int N = 6;
int n;
string A,B;
string a[N], b[N];
queue<string> qa, qb;
unordered_map<string,int> da, db;

int extend(queue<string>& q, unordered_map<string,int>& da, unordered_map<string,int>& db, string a[], string b[]){
    int d = da[q.front()];
    while(q.size() && da[q.front()] == d){
        auto t = q.front();
        q.pop();
        for (int i = 0; i < n; i++){
            for (int j = 0; j < t.size(); j++){
                if(t.substr(j, a[i].size()) == a[i]){
                    string r = t.substr(0,j) + b[i] + t.substr(j + a[i].size());
                    if(db.count(r)) return da[t] + db[r] + 1;
                    if(da.count(r)) continue;
                    q.push(r);
                    da[r] = da[t] + 1;
                }
            }
        }
    }
    return 11;
}

int bfs(){
    if(A == B) return 0;
    qa.push(A); qb.push(B);
    da[A] = 0; db[B] = 0;
    int step = 0;
    while(qa.size() && qb.size()){
        int t;
        if(qa.size() < qb.size()) t = extend(qa, da, db, a, b);
        else t = extend(qb, db, da, b, a);
        if(t <= 10) return t;
        if(++step == 10) return -1;
    }
    return -1;
}

int main(){
    cin >> A >> B;
    while(cin >> a[n] >> b[n]) n++;
    int t = bfs();
    if(t == -1) cout << "NO ANSWER!" << endl;
    else cout << t << endl;
    return 0;
}

단어 사슬 (Word Chain)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 21;

int n, m;
string word[N];
int ans;
int used[N];
int g[N][N];

void dfs(string dragon, int last){
    ans = max((int)dragon.size(), ans);
    used[last]++;
    for(int i = 0; i < n; i++){
        if(g[last][i] && used[i] < 2){
            dfs(dragon + word[i].substr(g[last][i]), i);
        }
    }
    used[last]--;
}

int main(){
    cin >> n;
    char start;
    for (int i = 0; i < n; i++) cin >> word[i];
    cin >> start;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
            for (int k = 1; k < min(word[i].size(), word[j].size()); k++)
                if(word[i].substr(word[i].size()-k, k) == word[j].substr(0, k)){
                    g[i][j] = k;
                    break;
                }
        }
    }
    for (int i = 0; i < n; i++){
        if(word[i][0] == start)
            dfs(word[i], i);
    }
    cout << ans;
    return 0;
}

십이지년도 차이 계산

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;

unordered_map<string, int> zodiac = {
    {"Ox", 0}, {"Tiger", 1}, {"Rabbit", 2},
    {"Dragon", 3}, {"Snake", 4}, {"Horse", 5},
    {"Goat", 6}, {"Monkey", 7}, {"Rooster", 8},
    {"Dog", 9}, {"Pig", 10}, {"Rat", 11}
};

int main(){
    unordered_map<string, int> offset;
    offset["Bessie"] = 0;
    int n;
    cin >> n;
    while (n--){
        string s[8];
        for (int i = 0; i < 8; i++) cin >> s[i];
        if (s[3] == "previous"){
            int x = offset[s[7]], y = zodiac[s[4]];
            int r = ((x - y) % 12 + 12) % 12;
            if (!r) r = 12;
            offset[s[0]] = x - r;
        }
        else {
            int x = offset[s[7]], y = zodiac[s[4]];
            int r = ((y - x) % 12 + 12) % 12;
            if (!r) r = 12;
            offset[s[0]] = x + r;
        }
    }
    cout << abs(offset["Elsie"]) << endl;
    return 0;
}

태그: KMP trie XOR 양방향BFS 문자열매칭

7월 17일 02:04에 게시됨