|
1 | 1 | /** |
2 | | - * Implementation of finding the longest paldindrome subsequence Time complexity: O(n^2) |
| 2 | + * Longest Palindrome Subsequence (LPS) |
| 3 | + * |
| 4 | + * <p>Given a string S, find the length of the longest subsequence in S that is also a palindrome. |
| 5 | + * |
| 6 | + * <p>Important: A subsequence is different from a substring. Subsequences do not need to be |
| 7 | + * contiguous. For example, in the string "BBBAB", the longest palindrome subsequence is "BBBB" with |
| 8 | + * length 4, whereas the longest palindrome substring is "BBB" with length 3. |
| 9 | + * |
| 10 | + * <p>Time Complexity: O(n^2) |
3 | 11 | * |
4 | 12 | * @author William Fiset, [email protected] |
5 | 13 | */ |
6 | 14 | package com.williamfiset.algorithms.dp; |
7 | 15 |
|
8 | 16 | public class LongestPalindromeSubsequence { |
9 | 17 |
|
10 | | - public static void main(String[] args) { |
11 | | - System.out.println(lps("bbbab")); // Outputs 4 since "bbbb" is valid soln |
12 | | - System.out.println(lps("bccd")); // Outputs 2 since "cc" is valid soln |
13 | | - } |
14 | | - |
15 | | - // Returns the length of the longest paldindrome subsequence |
16 | | - public static int lps(String s) { |
| 18 | + /** |
| 19 | + * Recursive implementation with memoization to find the length of |
| 20 | + * the longest palindrome subsequence. |
| 21 | + * |
| 22 | + * Time Complexity: O(n^2) |
| 23 | + * Space Complexity: O(n^2) |
| 24 | + */ |
| 25 | + public static int lpsRecursive(String s) { |
17 | 26 | if (s == null || s.length() == 0) return 0; |
18 | 27 | Integer[][] dp = new Integer[s.length()][s.length()]; |
19 | | - return lps(s, dp, 0, s.length() - 1); |
| 28 | + return lpsRecursive(s, dp, 0, s.length() - 1); |
20 | 29 | } |
21 | 30 |
|
22 | | - // Private recursive method with memoization to count |
23 | | - // the longest paldindrome subsequence. |
24 | | - private static int lps(String s, Integer[][] dp, int i, int j) { |
25 | | - |
26 | | - // Base cases |
| 31 | + private static int lpsRecursive(String s, Integer[][] dp, int i, int j) { |
27 | 32 | if (j < i) return 0; |
28 | 33 | if (i == j) return 1; |
29 | 34 | if (dp[i][j] != null) return dp[i][j]; |
30 | 35 |
|
31 | | - char c1 = s.charAt(i), c2 = s.charAt(j); |
| 36 | + if (s.charAt(i) == s.charAt(j)) { |
| 37 | + // If characters at both ends match, they form part of the palindrome. |
| 38 | + // We add 2 to the result and shrink the window from both sides (i+1, j-1). |
| 39 | + return dp[i][j] = lpsRecursive(s, dp, i + 1, j - 1) + 2; |
| 40 | + } |
| 41 | + // If characters don't match, we take the maximum by either: |
| 42 | + // 1. Skipping the left character (i+1) |
| 43 | + // 2. Skipping the right character (j-1) |
| 44 | + return dp[i][j] = Math.max(lpsRecursive(s, dp, i + 1, j), lpsRecursive(s, dp, i, j - 1)); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Iterative implementation (bottom-up) to find the length of |
| 49 | + * the longest palindrome subsequence. |
| 50 | + * |
| 51 | + * Time Complexity: O(n^2) |
| 52 | + * Space Complexity: O(n^2) |
| 53 | + */ |
| 54 | + public static int lpsIterative(String s) { |
| 55 | + if (s == null || s.isEmpty()) return 0; |
| 56 | + int n = s.length(); |
| 57 | + int[][] dp = new int[n][n]; |
| 58 | + |
| 59 | + // Every single character is a palindrome of length 1 |
| 60 | + for (int i = 0; i < n; i++) dp[i][i] = 1; |
32 | 61 |
|
33 | | - // Both end characters match |
34 | | - if (c1 == c2) return dp[i][j] = lps(s, dp, i + 1, j - 1) + 2; |
| 62 | + for (int len = 2; len <= n; len++) { |
| 63 | + for (int i = 0; i <= n - len; i++) { |
| 64 | + int j = i + len - 1; |
| 65 | + if (s.charAt(i) == s.charAt(j)) { |
| 66 | + // Characters match: use the result from the inner substring (i+1, j-1) and add 2. |
| 67 | + dp[i][j] = dp[i + 1][j - 1] + 2; |
| 68 | + } else { |
| 69 | + // Characters don't match: take the best result from either skipping the |
| 70 | + // left character (i+1) or the right character (j-1). |
| 71 | + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | + return dp[0][n - 1]; |
| 76 | + } |
| 77 | + |
| 78 | + public static void main(String[] args) { |
| 79 | + String s1 = "bbbab"; |
| 80 | + System.out.println(lpsRecursive(s1)); // 4 |
| 81 | + System.out.println(lpsIterative(s1)); // 4 |
35 | 82 |
|
36 | | - // Consider both possible substrings and take the maximum |
37 | | - return dp[i][j] = Math.max(lps(s, dp, i + 1, j), lps(s, dp, i, j - 1)); |
| 83 | + String s2 = "bccd"; |
| 84 | + System.out.println(lpsRecursive(s2)); // 2 |
| 85 | + System.out.println(lpsIterative(s2)); // 2 |
38 | 86 | } |
39 | 87 | } |
0 commit comments