|
2 | 2 | > |
3 | 3 | > Give an efficient algorithm to find the longest palindrome that is a subsequence of a given input string. For example, given the input $\text{character}$, your algorithm should return $\text{carac}$. What is the running time of your algorithm? |
4 | 4 |
|
5 | | -Let $A[1..n]$ denote the array which contains the given word. First note that for a palindrome to be a subsequence we must be able to divide the input word at some position $i$, and then solve the longest common subsequence problem on $A[1..i]$ and $A[i + 1..n]$, possibly adding in an extra letter to account for palindromes with a central letter. Since there are $n$ places at which we could split the input word and the $\text{LCS}$ problem takes time $O(n^2)$, we can solve the palindrome problem in time $O(n^3)$. |
| 5 | +Let $A[1..n]$ denote the array which contains the given word. Taking inspiration from the Longest-Common-Subsequence problem earlier in the chapter, we can consider each pair of indexes $i$ and $j$ such that $0 \le i < j < n$ as a decision point where each character-pair may be either included or excluded from the longest-palindromic substring. |
| 6 | + |
| 7 | +Our recursion is defined as: |
| 8 | + |
| 9 | +- If $A[i] = A[j]$, then characters $i$ and $j$ must be in the longest palindromic substring (else we contradict that our recursion solves for the longest possible substring) |
| 10 | +- If $A[i] \ne A[j]$, then the longest palindromic substring is found in one of $A[i + 1..j]$ or $A[i..j + 1]$, forming our recursive subproblems |
| 11 | + |
| 12 | +Our recursive base case is the single-character palindrome, i.e. the case where $i = j$. From there we build our subproblems up taking one character-length step at a time until we reach $n$ our size of the initial array. |
| 13 | + |
| 14 | +```cpp |
| 15 | +LPS-LENGTH(A, n) |
| 16 | + initialize a table LPS of size n by n |
| 17 | + initialize a table DECISIONS of size n by n |
| 18 | + |
| 19 | + for i = 1 to n |
| 20 | + // Base case, a palindrome of length 1 |
| 21 | + LPS[i][i] = 1 |
| 22 | + DECISIONS[i][i] = "TAKE ONE" |
| 23 | + |
| 24 | + for length = 1 to n |
| 25 | + for i = 0 to (n - length) |
| 26 | + j = i + length |
| 27 | + if A[i] == A[j] |
| 28 | + LPS[i][j] = 2 + LPS[i + 1][j - 1] |
| 29 | + DECISIONS[i][j] = "TAKE BOTH" |
| 30 | + else if LPS[i + 1][j] > LPS[i][j - 1] |
| 31 | + LPS[i][j] = LPS[i + 1][j] |
| 32 | + DECISIONS[i][j] = "IGNORE LEFT" |
| 33 | + else |
| 34 | + LPS[i][j] = LPS[i][j - 1] |
| 35 | + DECISIONS[i][j] = "IGNORE RIGHT" |
| 36 | + |
| 37 | + return LPS, DECISIONS |
| 38 | +``` |
| 39 | +
|
| 40 | +As is easily intuited by the above algorithm, our algorithm $O(n^2)$, needing to iterate over $n$ subproblem-lengths and having to compare $O(n)$ index pairs for each. |
| 41 | +
|
| 42 | +And then similarly to the Longest-Common-Subsequence problem again, we can traverse the $\text{DECISIONS}$ matrix to reconstruct our palindrome: |
| 43 | +
|
| 44 | +```cpp |
| 45 | +PRINT-LPS(A, DECISIONS, i, j) |
| 46 | + if DECISIONS[i][j] == "TAKE ONE": |
| 47 | + print A[i] |
| 48 | + else if DECISIONS[i][j] == "TAKE BOTH": |
| 49 | + print A[i] |
| 50 | + PRINT-LCS(A, DECISIONS, i + 1, j - 1) |
| 51 | + print A[j] |
| 52 | + else if DECISIONS[i][j] == "IGNORE LEFT": |
| 53 | + PRINT-LCS(A, DECISIONS, i + 1, j) |
| 54 | + else |
| 55 | + // IGNORE_RIGHT |
| 56 | + PRINT-LCS(A, DECISIONS, i, j - 1) |
| 57 | +``` |
0 commit comments