|
| 1 | +''' |
| 2 | +# 5. Longest Palindromic Substring |
| 3 | +
|
| 4 | +DP 테이블을 사용하여 팰린드롬 여부를 저장하고 최대 길이를 찾기. |
| 5 | +''' |
| 6 | +class Solution: |
| 7 | + ''' |
| 8 | + TC: O(n^2) |
| 9 | + SC: O(n^2) |
| 10 | + ''' |
| 11 | + def longestPalindrome(self, s: str) -> str: |
| 12 | + n = len(s) |
| 13 | + if n == 1: |
| 14 | + return s |
| 15 | + |
| 16 | + start, length = 0, 1 |
| 17 | + dp = [[False] * n for _ in range(n)] # SC: O(n^2) |
| 18 | + |
| 19 | + # length 1, diagonal elements in 2d array |
| 20 | + for i in range(n): # TC: O(n) |
| 21 | + dp[i][i] = True |
| 22 | + |
| 23 | + # length 2, are two elements same |
| 24 | + for i in range(n - 1): |
| 25 | + if s[i] == s[i + 1]: |
| 26 | + dp[i][i + 1] = True |
| 27 | + start, length = i, 2 |
| 28 | + |
| 29 | + # length 3+ |
| 30 | + for word_length in range(3, n + 1): # TC: O(n^2) |
| 31 | + for i in range(n - word_length + 1): |
| 32 | + j = i + word_length - 1 |
| 33 | + |
| 34 | + if s[i] == s[j] and dp[i + 1][j - 1]: |
| 35 | + dp[i][j] = True |
| 36 | + start, length = i, word_length |
| 37 | + |
| 38 | + return s[start:start + length] |
| 39 | + |
| 40 | +''' |
| 41 | +## Check Leetcode Hints |
| 42 | +- How can we reuse a previously computed palindrome to compute a larger palindrome? |
| 43 | + - use dp table that stores isPalindrome computation. |
| 44 | +- If “aba” is a palindrome, is “xabax” a palindrome? Similarly is “xabay” a palindrome? |
| 45 | + - if it is palindrome, we only need to check the outermost chars, that wrapping our palindrome. |
| 46 | +- Palindromic checks can be O(1) by reusing prev computations. |
| 47 | + - DP! |
| 48 | +
|
| 49 | +## 동적 프로그래밍 풀이 |
| 50 | +- s[i:j]의 팰린드롬 여부를 저장하기 위해서는 2차원 배열을 사용하는 것이 간편하다. |
| 51 | +- 길이가 1인 경우, 팰린드롬 |
| 52 | +- 길이가 2인 경우, 두 문자가 같다면 팰린드롬 |
| 53 | +- 3 이상은 dp 테이블을 활용하며 확인 |
| 54 | + - ex) 길이가 5인 문자열을 다음과 같이 탐색 |
| 55 | + ``` |
| 56 | + len: 3, i: 0, j: 2 |
| 57 | + len: 3, i: 1, j: 3 |
| 58 | + len: 3, i: 2, j: 4 |
| 59 | + len: 4, i: 0, j: 3 |
| 60 | + len: 4, i: 1, j: 4 |
| 61 | + len: 5, i: 0, j: 4 |
| 62 | + ``` |
| 63 | +
|
| 64 | +## 탐구 |
| 65 | +- can we use sliding window to optimize? |
| 66 | +슬라이딩 윈도우는 연속적인 구간을 유지하며 최적해를 찾을 때 유용하지만, 팰린드롬은 중앙 확장과 이전 계산 재사용이 핵심. |
| 67 | +
|
| 68 | +- can we solve this by counting char? |
| 69 | +문자 개수를 세면 "어떤 문자가 몇 번 나왔는지"만 알 수 있지만, 팰린드롬 여부는 문자 순서가 중요하다. |
| 70 | +
|
| 71 | +- 그렇다면 개선 방법은? |
| 72 | +> Manacher's Algorithm: https://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/ |
| 73 | +> |
| 74 | +>팰린드롬의 대칭성을 활용해 중앙을 기준으로 확장하는 Manacher's Algorithm을 적용할 수 있다. 모든 문자 사이에 #을 넣어 짝수, 홀수를 동일하게 다루는 기법인데 구현이 다소 복잡하다. 시간 복잡도는 O(n)이다. |
| 75 | +''' |
| 76 | +class Solution: |
| 77 | + ''' |
| 78 | + TC: O(n) |
| 79 | + SC: O(n) |
| 80 | + ''' |
| 81 | + def longestPalindromeManacher(self, s: str) -> str: |
| 82 | + ''' |
| 83 | + 1. 문자열 변환하기 (가운데에 # 추가) |
| 84 | + 2. 저장공간 초기화 |
| 85 | + 3. 변환된 문자열 순회하기 |
| 86 | + (a) 미러 인덱스 계산 |
| 87 | + (b) 팰린드롬 반지름 확장 |
| 88 | + - i에서 양쪽 문자들을 비교해서 확장 |
| 89 | + (c) 새로운 팰린드롬 찾기 |
| 90 | + - 새로운 팰린드롬을 i로 설정 |
| 91 | + - 새로운 팰린드롬의 오른쪽 끝을 i + p[i]로 설정 |
| 92 | + (d) 가장 긴 팰린드롬 업데이트 |
| 93 | + ''' |
| 94 | + transformed = '#' + '#'.join(s) + '#' |
| 95 | + n = len(transformed) |
| 96 | + p = [0] * n # i 중심의 팰린드롬 크기 |
| 97 | + c = 0 # 현재 팰린드롬의 중심 |
| 98 | + r = 0 # 현재 팰린드롬의 오른쪽 끝 |
| 99 | + max_len = 0 # 가장 긴 팰린드롬의 길이 |
| 100 | + center = 0 # 가장 긴 팰린드롬의 중심 |
| 101 | + |
| 102 | + for i in range(n): |
| 103 | + # 현재 위치 i의 미러 인덱스 (중앙을 기준으로 대칭되는 인덱스) |
| 104 | + # ex) ababbaa |
| 105 | + # 0123456 |
| 106 | + # i가 3이고 center가 2일 때, 2*2 - 3 = 1, 미러 인덱스는 1 |
| 107 | + # i가 5이고 center가 4일 때, 2*4 - 5 = 3, 미러 인덱스는 3 |
| 108 | + mirror = 2 * c - i |
| 109 | + |
| 110 | + if i < r: |
| 111 | + # r - i: 얼마나 더 확장 될 수 있는가 => 현재 팰린드롬의 오른쪽 끝에서 현재 인덱스까지의 거리 |
| 112 | + # p[mirror]: 미러 인덱스에서의 팰린드롬 반지름 |
| 113 | + p[i] = min(r - i, p[mirror]) |
| 114 | + # 작은 값만큼만 확장이 가능하다. 예를 들어, r - i가 더 작은 값이라면 팰린드롬을 그만큼만 확장할 수 있고, p[mirror]가 더 작은 값이라면 이미 그만큼 확장이 된 상태 |
| 115 | + # r - i가 3이고 p[mirror]가 2라면 팰린드롬을 2만큼 확장할 수 있고, r - i가 2이고 p[mirror]가 3이라면 팰린드롬을 2만큼 확장할 수 있다. |
| 116 | + |
| 117 | + # 현재 중심에서 팰린드롬을 확장 |
| 118 | + # 양 끝이 같다면 팰린드롬 반지름 p[i]를 1씩 증가 |
| 119 | + while i + p[i] + 1 < n and i - p[i] - 1 >= 0 and transformed[i + p[i] + 1] == transformed[i - p[i] - 1]: |
| 120 | + p[i] += 1 |
| 121 | + |
| 122 | + # 기존에 찾은 팰린드롬보다 더 큰 팰린드롬을 찾은 경우 |
| 123 | + # 현재 중심과 오른쪽 끝을 설정 |
| 124 | + if i + p[i] > r: |
| 125 | + c = i |
| 126 | + r = i + p[i] |
| 127 | + |
| 128 | + # 가장 긴 팰린드롬 업데이트 |
| 129 | + if p[i] > max_len: |
| 130 | + max_len = p[i] |
| 131 | + center = i |
| 132 | + |
| 133 | + # 변환된 문자열에서 가장 긴 팰린드롬을 원래 문자열에서 추출 |
| 134 | + start = (center - max_len) // 2 |
| 135 | + return s[start:start + max_len] |
0 commit comments