From 654b6007ff95b3435bb11a7ca82b7ca8e21c7c5c Mon Sep 17 00:00:00 2001 From: irenemartnez <117649832+irenemartnez@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:14:07 +0200 Subject: [PATCH 1/4] perf(strings): optimize LongestCommonSubstring DP memory to O(N) and add tests --- .../strings/LongestCommonSubstring.java | 12 +-- .../strings/LongestCommonSubstringTest.java | 97 ++++++++++++++++++- 2 files changed, 102 insertions(+), 7 deletions(-) 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..02ff742d7f09 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -4,7 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; - +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import java.time.Duration; public class LongestCommonSubstringTest { @Test @@ -33,4 +34,98 @@ 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 testVeryLargeInputsTimeoutFailure() { + // 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'); + } + + // Enforce a strict 50ms time limit which this O(N * M) computation will exceed + assertTimeoutPreemptively(Duration.ofMillis(50), () -> { + 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")); + } } From 521922bdae29d8ca3688be1ac1e809242ed69351 Mon Sep 17 00:00:00 2001 From: irenemartnez <117649832+irenemartnez@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:37:36 +0200 Subject: [PATCH 2/4] fix(strings): fix formatting, unicode characters and update performance test --- .../strings/LongestCommonSubstringTest.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java index 02ff742d7f09..1d89d65b05d9 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -2,10 +2,11 @@ // author: Vraj Prajapati @Rosander0 import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + import java.time.Duration; +import org.junit.jupiter.api.Test; + public class LongestCommonSubstringTest { @Test @@ -34,6 +35,7 @@ public void testMultipleMatchesFirstLongest() { // Keeps the first matched longest substring when lengths are tied assertEquals("abc", LongestCommonSubstring.longestCommonSubstring("abcXdef", "abcYdef")); } + // NEW @Test @@ -84,7 +86,7 @@ public void testLargeInputsPerformanceAndTimeout() { } @Test - public void testVeryLargeInputsTimeoutFailure() { + public void testVeryLargeInputsPerformance() { // Generate two very large strings (4,000 characters each) int size = 4000; StringBuilder sb1 = new StringBuilder(size); @@ -95,9 +97,8 @@ public void testVeryLargeInputsTimeoutFailure() { sb2.append('B'); } - // Enforce a strict 50ms time limit which this O(N * M) computation will exceed - assertTimeoutPreemptively(Duration.ofMillis(50), () -> { - LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString()); + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { + assertEquals("", LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString())); }); } @@ -113,11 +114,12 @@ public void testCaseSensitivityAndUnicode() { // 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")); } @@ -128,4 +130,4 @@ public void testOverlappingSubstrings() { assertEquals("AAAA", LongestCommonSubstring.longestCommonSubstring("AAAAA", "AAAA")); assertEquals("ABAB", LongestCommonSubstring.longestCommonSubstring("ABABAB", "CABAB")); } -} +} \ No newline at end of file From e3f3451e88bbf564521ac5d175002d9f585816ed Mon Sep 17 00:00:00 2001 From: irenemartnez <117649832+irenemartnez@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:43:55 +0200 Subject: [PATCH 3/4] style(strings): add trailing newline to fix Checkstyle and Clang-format --- .../com/thealgorithms/strings/LongestCommonSubstringTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java index 1d89d65b05d9..ef1f5397ab37 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -130,4 +130,4 @@ public void testOverlappingSubstrings() { assertEquals("AAAA", LongestCommonSubstring.longestCommonSubstring("AAAAA", "AAAA")); assertEquals("ABAB", LongestCommonSubstring.longestCommonSubstring("ABABAB", "CABAB")); } -} \ No newline at end of file +} From 63a4aba5d7749a89c9a3e55feb630fdcac6ba52e Mon Sep 17 00:00:00 2001 From: irenemartnez <117649832+irenemartnez@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:53:10 +0200 Subject: [PATCH 4/4] style(strings): inline lambda in testVeryLargeInputsPerformance for clang-format --- .../com/thealgorithms/strings/LongestCommonSubstringTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java index ef1f5397ab37..ea62a6b64741 100644 --- a/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/thealgorithms/strings/LongestCommonSubstringTest.java @@ -97,9 +97,7 @@ public void testVeryLargeInputsPerformance() { sb2.append('B'); } - assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { - assertEquals("", LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString())); - }); + assertTimeoutPreemptively(Duration.ofSeconds(2), () -> { assertEquals("", LongestCommonSubstring.longestCommonSubstring(sb1.toString(), sb2.toString())); }); } @Test