Skip to content

Commit 9680de3

Browse files
Merge branch 'master' into master
2 parents 9af8567 + 12935c2 commit 9680de3

File tree

7 files changed

+126
-20
lines changed

7 files changed

+126
-20
lines changed

spotbugs-exclude.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111
<Match>
1212
<Bug pattern="RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT" />
1313
</Match>
14-
<Match>
15-
<Bug pattern="DM_NEXTINT_VIA_NEXTDOUBLE" />
16-
</Match>
1714
<Match>
1815
<Bug pattern="EI_EXPOSE_REP" />
1916
</Match>
@@ -99,9 +96,6 @@
9996
<Match>
10097
<Bug pattern="UCPM_USE_CHARACTER_PARAMETERIZED_METHOD" />
10198
</Match>
102-
<Match>
103-
<Bug pattern="SUA_SUSPICIOUS_UNINITIALIZED_ARRAY" />
104-
</Match>
10599
<Match>
106100
<Bug pattern="SPP_USE_MATH_CONSTANT" />
107101
</Match>

src/main/java/com/thealgorithms/divideandconquer/ClosestPair.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ public static class Location {
6666
}
6767
}
6868

69-
public Location[] createLocation(int numberValues) {
70-
return new Location[numberValues];
71-
}
72-
7369
public Location buildLocation(double x, double y) {
7470
return new Location(x, y);
7571
}

src/main/java/com/thealgorithms/strings/LongestNonRepetitiveSubstring.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ private LongestNonRepetitiveSubstring() {
1313
/**
1414
* Finds the length of the longest substring without repeating characters.
1515
*
16+
* Uses the sliding window technique with a HashMap to track
17+
* the last seen index of each character.
18+
*
19+
* Time Complexity: O(n), where n is the length of the input string.
20+
* Space Complexity: O(min(n, m)), where m is the size of the character set.
21+
*
1622
* @param s the input string
1723
* @return the length of the longest non-repetitive substring
1824
*/
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.thealgorithms.strings;
2+
3+
/**
4+
* Finds the longest substring that occurs at least twice in a given string.
5+
*
6+
* <p>Uses the suffix array (via {@link SuffixArray}) and Kasai's algorithm
7+
* to build the LCP (Longest Common Prefix) array, then returns the substring
8+
* corresponding to the maximum LCP value.</p>
9+
*
10+
* <p>Time complexity: O(n log² n) for suffix array construction + O(n) for LCP.</p>
11+
*
12+
* @see <a href="https://en.wikipedia.org/wiki/Longest_repeated_substring_problem">Longest repeated substring problem</a>
13+
* @see SuffixArray
14+
*/
15+
public final class LongestRepeatedSubstring {
16+
17+
private LongestRepeatedSubstring() {
18+
}
19+
20+
/**
21+
* Returns the longest substring that appears at least twice in the given text.
22+
*
23+
* @param text the input string
24+
* @return the longest repeated substring, or an empty string if none exists
25+
*/
26+
public static String longestRepeatedSubstring(String text) {
27+
if (text == null || text.length() <= 1) {
28+
return "";
29+
}
30+
31+
final int[] suffixArray = SuffixArray.buildSuffixArray(text);
32+
final int[] lcp = buildLcpArray(text, suffixArray);
33+
34+
int maxLen = 0;
35+
int maxIdx = 0;
36+
for (int i = 0; i < lcp.length; i++) {
37+
if (lcp[i] > maxLen) {
38+
maxLen = lcp[i];
39+
maxIdx = suffixArray[i + 1];
40+
}
41+
}
42+
43+
return text.substring(maxIdx, maxIdx + maxLen);
44+
}
45+
46+
/**
47+
* Builds the LCP (Longest Common Prefix) array using Kasai's algorithm.
48+
*
49+
* <p>LCP[i] is the length of the longest common prefix between the suffixes
50+
* at positions suffixArray[i] and suffixArray[i+1] in sorted order.</p>
51+
*
52+
* @param text the original string
53+
* @param suffixArray the suffix array of the string
54+
* @return the LCP array of length n-1
55+
*/
56+
static int[] buildLcpArray(String text, int[] suffixArray) {
57+
final int n = text.length();
58+
final int[] rank = new int[n];
59+
final int[] lcp = new int[n - 1];
60+
61+
for (int i = 0; i < n; i++) {
62+
rank[suffixArray[i]] = i;
63+
}
64+
65+
int k = 0;
66+
for (int i = 0; i < n; i++) {
67+
if (rank[i] == n - 1) {
68+
k = 0;
69+
continue;
70+
}
71+
final int j = suffixArray[rank[i] + 1];
72+
while (i + k < n && j + k < n && text.charAt(i + k) == text.charAt(j + k)) {
73+
k++;
74+
}
75+
lcp[rank[i]] = k;
76+
if (k > 0) {
77+
k--;
78+
}
79+
}
80+
81+
return lcp;
82+
}
83+
}

src/test/java/com/thealgorithms/divideandconquer/ClosestPairTest.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ public void testBuildLocation() {
1616
assertEquals(4.0, point.y);
1717
}
1818

19-
@Test
20-
public void testCreateLocation() {
21-
ClosestPair cp = new ClosestPair(5);
22-
ClosestPair.Location[] locations = cp.createLocation(5);
23-
assertNotNull(locations);
24-
assertEquals(5, locations.length);
25-
}
26-
2719
@Test
2820
public void testXPartition() {
2921
ClosestPair cp = new ClosestPair(5);

src/test/java/com/thealgorithms/searches/LinearSearchThreadTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.junit.jupiter.api.Assertions.assertFalse;
44
import static org.junit.jupiter.api.Assertions.assertTrue;
55

6+
import java.util.Random;
67
import org.junit.jupiter.api.Test;
78

89
class LinearSearchThreadTest {
@@ -62,10 +63,11 @@ void testSearcherEmptySegment() throws InterruptedException {
6263
void testSearcherRandomNumbers() throws InterruptedException {
6364
int size = 200;
6465
int[] array = new int[size];
66+
Random random = new Random();
6567
for (int i = 0; i < size; i++) {
66-
array[i] = (int) (Math.random() * 100);
68+
array[i] = random.nextInt(100);
6769
}
68-
int target = array[(int) (Math.random() * size)]; // Randomly select a target that is present
70+
final int target = array[random.nextInt(size)]; // Randomly select a target that is present
6971
Searcher searcher = new Searcher(array, 0, size, target);
7072
searcher.start();
7173
searcher.join();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.thealgorithms.strings;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
6+
import java.util.stream.Stream;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
10+
11+
class LongestRepeatedSubstringTest {
12+
13+
@ParameterizedTest(name = "\"{0}\" -> \"{1}\"")
14+
@MethodSource("provideTestCases")
15+
void testLongestRepeatedSubstring(String input, String expected) {
16+
assertEquals(expected, LongestRepeatedSubstring.longestRepeatedSubstring(input));
17+
}
18+
19+
private static Stream<Arguments> provideTestCases() {
20+
return Stream.of(Arguments.of("banana", "ana"), Arguments.of("abcabc", "abc"), Arguments.of("aaaa", "aaa"), Arguments.of("abcd", ""), Arguments.of("a", ""), Arguments.of("", ""), Arguments.of(null, ""), Arguments.of("aab", "a"), Arguments.of("aa", "a"), Arguments.of("mississippi", "issi"));
21+
}
22+
23+
@ParameterizedTest(name = "\"{0}\" -> LCP={1}")
24+
@MethodSource("provideLcpTestCases")
25+
void testBuildLcpArray(String input, int[] expectedLcp) {
26+
int[] suffixArray = SuffixArray.buildSuffixArray(input);
27+
assertArrayEquals(expectedLcp, LongestRepeatedSubstring.buildLcpArray(input, suffixArray));
28+
}
29+
30+
private static Stream<Arguments> provideLcpTestCases() {
31+
return Stream.of(Arguments.of("banana", new int[] {1, 3, 0, 0, 2}), Arguments.of("ab", new int[] {0}));
32+
}
33+
}

0 commit comments

Comments
 (0)