cs

Manacher 알고리즘

2025-10-05
3분 분량
알고리즘

팰린드롬 (Palindrome)

팰린드롬은 문자열을 앞에서 읽으나 뒤에서 읽으나 동일한 형태로 읽히는 문자열을 의미한다.

예를 들어 아래와 같다.

가장 긴 팰린드롬 구하기

그렇다면 임의의 문자열에서 가장 긴 팰린드롬을 구하려면 어떻게 해야할까?

BANANA 라는 문자열을 생각해보자.

여기서 가장 긴 팰린드롬은 ANANA가 될 것이다.

팰린드롬을 가장 쉽게 구하는 방법은 아래 처럼 가운데부터 순차적으로 확인하는 것이다.

BANANA

BANANA

BANANA

각 인덱스마다 좌우를 확장하면서 비교하기 때문에 최악의 경우 모든 문자 쌍을 확인해야 한다.

따라서 시간 복잡도는 O(N2)O(N^2)이 되어 문자열이 길어진다면 비효율적인 방법이 될 것이다.

Manacher 알고리즘

이러한 비효율성을 해결하기 위해 고안된 것이 바로 Manacher 알고리즘이다.

Manacher 알고리즘은 중복된 연산을 줄여 모든 위치에서의 팰린드롬 길이를 선형 시간 O(N2)O(N^2) 안에 계산할 수 있다.

덕분에 긴 문자열에서도 효율적으로 가장 긴 팰린드롬을 찾을 수 있다.

1. 아이디어: "대칭성을 이용하자"

우선 홀수 길이의 팰린드롬만 생각해보자.

홀수 팰린드롬은 항상 중심 문자를 기준으로 양쪽이 대칭이다.

예를 들어 BANANA의 ANANA를 보면,

B A N A N A ↑ 중심 (A)

A를 중심으로 왼쪽(N A)과 오른쪽(A N)이 대칭인 걸 볼 수 있다.

이제 우리는 각 문자를 중심으로 가장 긴 팰린드롬의 반지름 길이(양쪽으로 얼마나 확장되는지)를 구하고 싶다.


2. 기존 방식의 한계

보통은 중심을 하나씩 옮기면서 좌우를 비교한다.

하지만 이건 O(N2)O(N^2)이 걸린다.

그래서 Manacher 알고리즘은 이미 구한 팰린드롬의 대칭성을 활용해서 불필요한 탐색을 건너뛴다.


3. 핵심 아이디어: "거울 위치"

지금까지 찾은 가장 긴 팰린드롬이 있다고 하자.

예를 들어,

  • 현재까지 찾은 가장 긴 팰린드롬: A를 중심으로 한 ANANA
  • 현재 살펴보는 문자: 그 오른쪽에 있는 다섯 번째 문자 N

B A N A N A

↑

(탐색 중인 문자)

이제 현재 중심 A을 기준으로 대칭되는 문자(거울 문자)를 찾아본다. 그 문자는 왼쪽의 N이다.

B A [N] A [N] A

  ↕ ↕

대칭 문자

왼쪽 N을 중심으로 한 팰린드롬의 길이는 이미 알고 있고, 현재 위치의 N은 그 팰린드롬의 안쪽에 있으므로 일부 길이는 그대로 사용할 수 있다.

즉, 이미 구한 대칭 정보를 이용해서 새로운 중심에서의 팰린드롬 길이를 빠르게 추정할 수 있다.

따라서 우리는 모든 문자를 처음부터 다시 비교하지 않아도 된다!


4. 알고리즘 동작 요약

  1. 문자열을 왼쪽부터 하나씩 보면서,
  2. 탐색 중인 위치(i)가 right 안쪽이라면,
  3. 그다음, 남은 부분만 필요할 때만 확장 탐색한다.
  4. 새로 찾은 팰린드롬이 기존보다 오른쪽으로 나가면 center, right를 갱신한다.

이 과정을 반복하면 모든 위치의 팰린드롬 길이를 한 번의 순회로 구할 수 있다.

코드 예시

java
public static int manacher(String s) {
    // 1. 예외 처리
    if (s == null || s.length() <= 1) {
        return s.length();
    }

    int len = s.length();

    // 2. 전처리: 문자열 사이에 '#' 삽입 (짝수 팰린드롬 처리를 위해)
    // 예: "BANANA" → "*#B#A#N#A#N#A#@"
    StringBuilder sb = new StringBuilder(len * 2 + 3);
    sb.append('*');
    for (int i = 0; i < len; i++) {
        sb.append('#').append(s.charAt(i));
    }
    sb.append("#@"); // 끝 표시용 문자

    char[] T = sb.toString().toCharArray();
    int tLen = T.length;
    int[] P = new int[tLen]; // 각 인덱스에서의 팰린드롬 반지름 길이

    int center = 0, right = 0;
    int answer = 0;

    // 3. 메인 루프
    for (int i = 1; i < tLen - 1; i++) {
        int mirror = 2 * center - i; // 현재 중심에 대한 대칭 인덱스

        // 이미 탐색된 범위 안이라면, 대칭 위치의 결과를 일부 복사
        if (i < right)
            P[i] = Math.min(right - i, P[mirror]);
        else
            P[i] = 0;

        // 좌우 확장
        while (T[i - 1 - P[i]] == T[i + 1 + P[i]]) {
            P[i]++;
        }

        // right와 center 갱신
        if (i + P[i] > right) {
            center = i;
            right = i + P[i];
        }

        // 최장 팰린드롬 길이 갱신
        answer = Math.max(answer, P[i]);
    }

    return answer;
}

6. 시간 복잡도는 왜 O(N)일까?

겉보기엔 여전히 각 문자마다 확장하니까 O(N2)O(N^2)처럼 보일 수도 있다.

하지만 실제로는 그렇지 않다.

이유는 간단하다.

  • right는 항상 한 방향(오른쪽) 으로만 이동한다.
  • 한 문자가 여러 번 확장 검사되는 일은 없다.

즉, 모든 문자에 대해 확장 시도는 최대 N번만 일어난다.

따라서 전체 시간 복잡도는 O(N)O(N)이 된다

함께 읽으면 좋은 글

CS2025-10-08

Kadane 알고리즘

“Kadane 알고리즘”은 연속된 부분 수열의 최대 합(Subarray Sum) 을 가장 효율적으로 구할 수 있는 알고리즘이다. 예를 들어 이런 문제가 있다. “정수 배열이 주어졌을 때, 연속된 부분 구간의 합 중 최대값을 구하시오.” 다음 수열을 보자.

CS2025-06-10

[컴퓨터비전] 12. 장면 이해

1981년 노벨의학상은 Hubel과 Wiesel의 시각 신경망 연구에 수여됨 이들은 고양이의 시각 피질에 마이크로 전극을 삽입하여, 특정 에지 방향에 반응하는 뉴런이 존재함을 발견 이는 시각 피질이 에지 정보를 추출하는 구조로 되어 있음을 의미 깡충 거미는 네 쌍의 눈으로…

CS2025-06-10

[컴퓨터비전] 11. 3차원 비전

본질 영상은 명암값에서 조명, 그림자 등의 외부 요인을 제거하고 물체의 고유한 성질만을 표현한 영상 사람은 외관과 본질을 구별할 수 있지만 컴퓨터는 일반적으로 외관만을 인식 본질 영상의 주요 예: Barrow(1978)가 본질 영상의 개념을 제안했지만, 명암 영상에서 본질…

CS2025-06-09

[컴퓨터비전] 10. 모션

현실 세계는 정적인 이미지보다 움직이는 장면이 훨씬 많음 움직임은 두 가지로 나뉨: 움직임 분석을 통해 동작 인식, 물체 추적, 장면 이해 등 다양한 작업이 가능 대표적인 응용: 광류: 연속된 영상 프레임 사이에서 화소의 움직임을 추정하는 기법 기본 가정: 밝기 불변 가정…