[PS] Manacher 알고리즘 (Manacher's Algorithm)

2023. 5. 5. 16:08·Problem solving


Manacher's algorithm

문자열이 주어지면, 포함된 부분 문자열의 회문(팰린드롬) 여부를 빠르게 찾아내는 알고리즘으로, 각각의 문자(i번째)를 중심으로 하는 가장 긴 팰린드롬의  반지름을 dp테이블에 저장하는 방식으로 구현한다.
이 알고리즘의 경우, 특정 문자를 중심으로 가지는 팰린드롬을 찾기 때문에 문자의 개수가 홀수개인 부분 문자열의 회문 여부만을 찾아낼 수 있는데, 짝수 길이의 부분 문자열의 회문여부도 찾고 싶으면 각 문자 사이에 “#”와 같은 더미 문자를 넣어서 구현하면 된다. 예) 1,2,2,1 -> #,1,#,2,#,2,#,1

O(N)의 시간 복잡도로 부분 문자열의 회문 여부를 찾아낼 수 있다.

각각의 문자를 중심으로 하는 가장 긴 팰린드롬의 반지름을 저장하는 dp테이블을 d라고 하면,
s = [1,2,1,3,1,2,1]의 에서 s[1]=2을 중심으로 하는 가장 긴 팰린드롬은 1,2,1이므로 반지름은 1이다. (중심 제외)
이와 같은 방식으로 dp테이블을 채워보면, d = [0,0,1,3,0,1,0]이다.

j < i인 모든 j에 대하여
left = min(j-d[j]), right = max(j+d[j])
center = (left+right)/2    <- left와 right를 만드는 j
i'은 center을 기준으로 i와 대칭인 점
이라고 하고, 가능한 Case를 분류해 보자.


j 중심으로 하는 팰린드롬의 대칭성을 활용하여 d[i]를 빠르게 찾아낼 수 있는 경우 연산을 스킵하고 dp 테이블을 채우는 것이 이 알고리즘의 핵심이다.
Case1의 경우 대칭성을 온전히 활용할 수 있다.
Case2의 경우 대칭성을 활용할 수 있는 부분은 i~right이고, right~i+d[i]까지는 naive한 방식으로 찾아 내야 한다.
Case3의 경우 대칭성을 활용할 수 있는 부분이 없으므로 전부 naive한 방식으로 구현하여야 한다.

핵심 원리

left ~ right는 j를 중심으로 하는 팰린드롬이고,
i'과 i는 j를 중심으로 대칭이므로, left~i'과 i~right는 j를 중심으로 대칭이다. 따라서 이 안에 포함되는 i’-d[i']~i'과 i~i+d[i]도 대칭이다.
d[i]와 d[i']은 각각 팰린드롬의 반지름을 나타내는 값이고, i와 i'은 팰린드롬의 중심이 되는 값인데, 위의 결과를 바탕으로 중심을 기준으로 양쪽을 생각해 보면, Case 1의 경우 대칭성을 활용하여 d[i]와 d[i']이 같음을 알 수 있다.

# Manacher's Algorithm, O(n) 

import sys
n = int(input())
s = list(map(int,sys.stdin.readline().split()))
d = [0 for _ in range(n)]
right = 0; center = 0
for i in range(n):
    if i <= right:
        d[i] = min(d[2*center-i], right-i)
        # min 값을 저장하기 때문에 i+d[i']가 right보다 클 수 없다.
        # 조건(i<=right and i+d[i'] <right)을 만족하는 경우에만
        # 기존 dp테이블에서 값을 가져오고, 나머지의 경우에는 naive한 방식으로 dp테이블을 채운다.
    while (i-d[i] > 0 and i+d[i] < n-1) and (s[i-(d[i]+1)] == s[i+(d[i]+1)]):
        d[i] += 1 
        # ex) i=0,d[i]=0일 경우 d[i]가 1이 되면 안 됨!
        # s[i-(d[i]+1)] == s[i+(d[i]+1)]을 미리 확인 해 본 후 d[i]를 1 키워준다 
    if i + d[i] > right:
        right = i + d[i]  # right의 최댓값 갱신
        center = i  # 다음 i부터 적용됨 (j<i)
print(d)


d[i] = min(d[i'], right-i)
만약 d[i'] < right-i라면,  
0 <= d[i'] < right-i이므로 i < right이고 i + d[i'] < right이므로 Case1을 만족하므로 d[i] = d[i']
만약 d[i'] > right-i라면,
Case1의 범위를 벗어나게 되지만, i~right까지의 대칭성은 보장하므로
초기 d[i]값을 right-i로 두고 이후의 값은 naive한 방식으로 확인하면 된다.


'Problem solving' 카테고리의 다른 글
  • [PS] 최장 증가 부분 수열 (LIS)
  • [PS] boj_10942 : 팰린드롬?
  • [PS] boj_12852 : 1로 만들기 2
  • [PS] boj_2887 : 행성 터널
vysryoo
vysryoo
  • vysryoo
    vysryoo
    vysryoo
  • 전체
    오늘
    어제
    • 분류 전체보기 (129)
      • Python (20)
      • Data structure (12)
      • Algorithm (14)
      • Operating system (18)
      • Programming language theory (12)
      • Computer architecture (6)
      • Softeware engineering (8)
      • Multicore (2)
      • Data Base (3)
      • Problem solving (24)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
vysryoo
[PS] Manacher 알고리즘 (Manacher's Algorithm)
상단으로

티스토리툴바