Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++로 반복되는 부분 문자열 패턴 확인하기

문제 소개

비어 있지 않은 문자열이 하나 주어집니다. 이때 해당 문자열을 자기 자신의 부분 문자열 하나를 골라 여러 번 이어 붙여서 만들 수 있는지 확인해야 합니다. 문자열은 소문자 영어 알파벳으로만 구성되며, 길이는 10,000을 넘지 않는다고 가정합니다.

예를 들어 입력이 "abaabaaba"라면 정답은 true입니다. 이 문자열은 "aba"를 세 번 반복하여 만들 수 있기 때문입니다.

풀이 접근 방식

이 문제는 KMP 알고리즘에 사용되는 실패 함수, 즉 LPS(Longest Proper Prefix which is also Suffix) 배열을 응용한 동적 프로그래밍(DP)으로 효율적으로 해결할 수 있습니다. 단계별 과정은 다음과 같습니다.

  • 동적 프로그래밍 접근 방식을 사용합니다.
  • 문자열 길이와 같은 크기 n의 DP 배열을 선언합니다.
  • i := 1, j := 0으로 초기화합니다.
  • i < n인 동안 아래를 반복합니다.
    • str[i] == str[j]라면 DP[i] := j + 1로 설정하고, i와 j를 각각 1씩 증가시킵니다.
    • 일치하지 않는 경우,
      • j > 0이면 j := DP[j - 1]로 되돌립니다.
      • 그렇지 않다면 dp[i] := 0으로 설정하고 i를 1 증가시킵니다.
  • DP[n - 1]이 0이 아니고, n % (n - DP[n - 1]) == 0을 만족하면 true를 반환합니다.
  • 그 외의 경우에는 false를 반환합니다.

핵심 아이디어는 다음과 같습니다. LPS 배열의 마지막 값은 문자열 전체에서 '접두사이면서 동시에 접미사'가 될 수 있는 가장 긴 부분의 길이를 의미합니다. 따라서 n이 (n - LPS[n-1]) 값으로 나누어떨어진다면, 문자열은 길이 (n - LPS[n-1])짜리 패턴이 반복된 형태라고 확신할 수 있습니다.

C++ 구현 예제

아래 코드를 통해 실제 구현을 살펴보겠습니다.

#include <bits/stdc++.h>
using namespace std;
class Solution {
    public:
    bool repeatedSubstringPattern(string s) {
        int n = s.size();
        vector<int> dp(n);
        int i = 1;
        int j = 0;
        while(i < n){
            if(s[i] == s[j]){
                dp[i] = j + 1;
                i++;
                j++;
            } else {
                if(j > 0){
                    j = dp[j - 1];
                } else {
                    dp[i] = 0;
                    i++;
                }
            }
        }
        return dp[n - 1] != 0 && n % (n - dp[n - 1]) == 0;
    }
};
int main(){
    Solution ob;
    string res = ob.repeatedSubstringPattern("abaabaaba") ? "true" : "false";
    cout << res;
    return 0;
}

입력

"abaabaaba"

출력

true

복잡도 분석

시간 복잡도는 O(n)입니다. 문자열을 한 번만 순회하면서 LPS 배열을 채우기 때문입니다. 공간 복잡도 역시 LPS 배열을 저장하기 위해 O(n)이 필요합니다. 이 방식은 단순히 모든 가능한 부분 문자열을 시도하는 브루트 포스(O(n²))보다 훨씬 효율적이므로, 길이가 최대 10,000인 입력에서도 안정적으로 동작합니다.