diff --git a/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java index b2190316aff2..27aef64ee883 100644 --- a/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java +++ b/src/main/java/com/thealgorithms/strings/LongestCommonSubstring.java @@ -29,20 +29,20 @@ public static String longestCommonSubstring(final String a, final String b) { return ""; } - int[][] dp = new int[a.length() + 1][b.length() + 1]; + int[] dp = new int[b.length() + 1]; int maxLength = 0; int endIndex = 0; for (int i = 1; i <= a.length(); i++) { - for (int j = 1; j <= b.length(); j++) { + for (int j = b.length(); j >= 1; j--) { if (a.charAt(i - 1) == b.charAt(j - 1)) { - dp[i][j] = dp[i - 1][j - 1] + 1; - if (dp[i][j] > maxLength) { - maxLength = dp[i][j]; + dp[j] = dp[j - 1] + 1; + if (dp[j] > maxLength) { + maxLength = dp[j]; endIndex = i; } } else { - dp[i][j] = 0; + dp[j] = 0; } } } diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java index e54abcf2f1f3..ea62a6b64741 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -2,7 +2,9 @@ // author: Vraj Prajapati @Rosander0 import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import java.time.Duration; import org.junit.jupiter.api.Test; public class LongestCommonSubstringTest { @@ -33,4 +35,97 @@ public void testMultipleMatchesFirstLongest() { // Keeps the first matched longest substring when lengths are tied assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abcXdef", "abcYdef")); } + + // NEW + + @Test + public void testSpacesAndSpecialCharacters() { + assertEquals(" Hello World! ", LongestCommonSubstring.longestCommonSubstring("123 Hello World! 456", "ABC Hello World! XYZ")); + assertEquals("@#$%^", LongestCommonSubstring.longestCommonSubstring("test@#$%^123", "abc@#$%^xyz")); + } + + @Test + public void testCoincidenceAtBoundaries() { + // Match at the beginning + assertEquals("PREFIX_", LongestCommonSubstring.longestCommonSubstring("PREFIX_12345", "PREFIX_67890")); + // Match at the end + assertEquals("_SUFFIX", LongestCommonSubstring.longestCommonSubstring("12345_SUFFIX", "67890_SUFFIX")); + } + + @Test + public void testRepeatedPatterns() { + assertEquals("anabanana", LongestCommonSubstring.longestCommonSubstring("bananabanana", "anabanana")); + } + + @Test + public void testLargeInputsPerformanceAndTimeout() { + // Generate two 3,000-character strings containing a common substring in the middle + int size = 3000; + StringBuilder sb1 = new StringBuilder(size); + StringBuilder sb2 = new StringBuilder(size); + + for (int i = 0; i < 1000; i++) { + sb1.append('A'); + sb2.append('B'); + } + + String commonPart = "COMMON_LONG_SUBSTRING_TEST_1234567890"; + sb1.append(commonPart); + sb2.append(commonPart); + + for (int i = 0; i < 1500; i++) { + sb1.append('X'); + sb2.append('Y'); + } + + // Verify that the algorithm completes within 2 seconds + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { + String result = LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString()); + assertEquals(commonPart, result); + }); + } + + @Test + public void testVeryLargeInputsPerformance() { + // Generate two very large strings (4,000 characters each) + int size = 4000; + StringBuilder sb1 = new StringBuilder(size); + StringBuilder sb2 = new StringBuilder(size); + + for (int i = 0; i < size; i++) { + sb1.append('A'); + sb2.append('B'); + } + + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { assertEquals("", LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString())); }); + } + + @Test + public void testCaseSensitivityAndUnicode() { + // Case sensitivity test + assertEquals("ABC", LongestCommonSubstring.longestCommonSubstring("ABCdef", "123ABCxyz")); + assertEquals("", LongestCommonSubstring.longestCommonSubstring("abc", "ABC")); + + // Full substring containment + assertEquals("inside", LongestCommonSubstring.longestCommonSubstring("inside", "text_inside_here")); + + // Unicode characters + assertEquals("_áéíóú_", LongestCommonSubstring.longestCommonSubstring("hola_áéíóú_mundo", "test_áéíóú_abc")); + } + + @Test + public void testWhitespaceAndControlCharacters() { + // Test with newlines and tabs + assertEquals("\t\n", LongestCommonSubstring.longestCommonSubstring("start\t\nend", "begin\t\nfinish")); + + // Test with multiple consecutive spaces + assertEquals(" ", LongestCommonSubstring.longestCommonSubstring("a b", "x y")); + } + + @Test + public void testOverlappingSubstrings() { + // Test overlapping matches like "AAAA" in "AAAAA" vs "AAAA" + assertEquals("AAAA", LongestCommonSubstring.longestCommonSubstring("AAAAA", "AAAA")); + assertEquals("ABAB", LongestCommonSubstring.longestCommonSubstring("ABABAB", "CABAB")); + } }