Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,39 +1,87 @@
/**
* Implementation of finding the longest paldindrome subsequence Time complexity: O(n^2)
* Longest Palindrome Subsequence (LPS)
*
* <p>Given a string S, find the length of the longest subsequence in S that is also a palindrome.
*
* <p>Important: A subsequence is different from a substring. Subsequences do not need to be
* contiguous. For example, in the string "BBBAB", the longest palindrome subsequence is "BBBB" with
* length 4, whereas the longest palindrome substring is "BBB" with length 3.
*
* <p>Time Complexity: O(n^2)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.dp;

public class LongestPalindromeSubsequence {

public static void main(String[] args) {
System.out.println(lps("bbbab")); // Outputs 4 since "bbbb" is valid soln
System.out.println(lps("bccd")); // Outputs 2 since "cc" is valid soln
}

// Returns the length of the longest paldindrome subsequence
public static int lps(String s) {
/**
* Recursive implementation with memoization to find the length of
* the longest palindrome subsequence.
*
* Time Complexity: O(n^2)
* Space Complexity: O(n^2)
*/
public static int lpsRecursive(String s) {
if (s == null || s.length() == 0) return 0;
Integer[][] dp = new Integer[s.length()][s.length()];
return lps(s, dp, 0, s.length() - 1);
return lpsRecursive(s, dp, 0, s.length() - 1);
}

// Private recursive method with memoization to count
// the longest paldindrome subsequence.
private static int lps(String s, Integer[][] dp, int i, int j) {

// Base cases
private static int lpsRecursive(String s, Integer[][] dp, int i, int j) {
if (j < i) return 0;
if (i == j) return 1;
if (dp[i][j] != null) return dp[i][j];

char c1 = s.charAt(i), c2 = s.charAt(j);
if (s.charAt(i) == s.charAt(j)) {
// If characters at both ends match, they form part of the palindrome.
// We add 2 to the result and shrink the window from both sides (i+1, j-1).
return dp[i][j] = lpsRecursive(s, dp, i + 1, j - 1) + 2;
}
// If characters don't match, we take the maximum by either:
// 1. Skipping the left character (i+1)
// 2. Skipping the right character (j-1)
return dp[i][j] = Math.max(lpsRecursive(s, dp, i + 1, j), lpsRecursive(s, dp, i, j - 1));
}

/**
* Iterative implementation (bottom-up) to find the length of
* the longest palindrome subsequence.
*
* Time Complexity: O(n^2)
* Space Complexity: O(n^2)
*/
public static int lpsIterative(String s) {
if (s == null || s.isEmpty()) return 0;
int n = s.length();
int[][] dp = new int[n][n];

// Every single character is a palindrome of length 1
for (int i = 0; i < n; i++) dp[i][i] = 1;

// Both end characters match
if (c1 == c2) return dp[i][j] = lps(s, dp, i + 1, j - 1) + 2;
for (int len = 2; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
if (s.charAt(i) == s.charAt(j)) {
// Characters match: use the result from the inner substring (i+1, j-1) and add 2.
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
// Characters don't match: take the best result from either skipping the
// left character (i+1) or the right character (j-1).
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}

public static void main(String[] args) {
String s1 = "bbbab";
System.out.println(lpsRecursive(s1)); // 4
System.out.println(lpsIterative(s1)); // 4

// Consider both possible substrings and take the maximum
return dp[i][j] = Math.max(lps(s, dp, i + 1, j), lps(s, dp, i, j - 1));
String s2 = "bccd";
System.out.println(lpsRecursive(s2)); // 2
System.out.println(lpsIterative(s2)); // 2
}
}
11 changes: 11 additions & 0 deletions src/test/java/com/williamfiset/algorithms/dp/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,16 @@ java_test(
deps = TEST_DEPS,
)

# bazel test //src/test/java/com/williamfiset/algorithms/dp:LongestPalindromeSubsequenceTest
java_test(
name = "LongestPalindromeSubsequenceTest",
srcs = ["LongestPalindromeSubsequenceTest.java"],
main_class = "org.junit.platform.console.ConsoleLauncher",
use_testrunner = False,
args = ["--select-class=com.williamfiset.algorithms.dp.LongestPalindromeSubsequenceTest"],
runtime_deps = JUNIT5_RUNTIME_DEPS,
deps = TEST_DEPS,
)

# Run all tests
# bazel test //src/test/java/com/williamfiset/algorithms/dp:all
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.williamfiset.algorithms.dp;

import static com.google.common.truth.Truth.assertThat;
import org.junit.jupiter.api.Test;

public class LongestPalindromeSubsequenceTest {

@Test
public void testLps() {
String s1 = "bbbab";
assertThat(LongestPalindromeSubsequence.lpsRecursive(s1)).isEqualTo(4);
assertThat(LongestPalindromeSubsequence.lpsIterative(s1)).isEqualTo(4);

String s2 = "bccd";
assertThat(LongestPalindromeSubsequence.lpsRecursive(s2)).isEqualTo(2);
assertThat(LongestPalindromeSubsequence.lpsIterative(s2)).isEqualTo(2);

String s3 = "abcde";
assertThat(LongestPalindromeSubsequence.lpsRecursive(s3)).isEqualTo(1);
assertThat(LongestPalindromeSubsequence.lpsIterative(s3)).isEqualTo(1);

String s4 = "aaaaa";
assertThat(LongestPalindromeSubsequence.lpsRecursive(s4)).isEqualTo(5);
assertThat(LongestPalindromeSubsequence.lpsIterative(s4)).isEqualTo(5);
}

@Test
public void testEmptyStrings() {
assertThat(LongestPalindromeSubsequence.lpsRecursive("")).isEqualTo(0);
assertThat(LongestPalindromeSubsequence.lpsIterative("")).isEqualTo(0);
}

@Test
public void testNullInputs() {
assertThat(LongestPalindromeSubsequence.lpsRecursive(null)).isEqualTo(0);
assertThat(LongestPalindromeSubsequence.lpsIterative(null)).isEqualTo(0);
}
}
Loading