Binary Search Boyer–Moore Extended KMP Hash Function Rolling Hash Sliding Window String Suffix Array Suffix Automato Suffix Tree
Description Given a string s, consider all duplicated substrings : (contiguous) substrings of s that occur 2 or more times. The occurrences may overlap.
Return any duplicated substring that has the longest possible length. If s does not have a duplicated substring, the answer is "".
Example 1:
Input: s = "banana"
Output: "ana"
Example 2:
Input: s = "abcd"
Output: ""
Constraints:
2 <= s.length <= 3 * 104 s consists of lowercase English letters. Solutions Solution 1 Thinking
Inserting every substring into a set is quadratic and fails for \(n\le 3\times 10^4\) . If a duplicate of length \(L\) exists, shorter duplicates exist as well, so length is monotonic.
Binary search the length. The check inserts every slice of length \(\textit{mid}\) into a set and returns on the first collision. A hit tries a longer length; a miss shortens it.
The implementation uses ordinary Python slices and a hash set, remembering the last hit as the answer.
Python3 Java C++ Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 class Solution :
def longestDupSubstring ( self , s : str ) -> str :
def check ( l ):
vis = set ()
for i in range ( n - l + 1 ):
t = s [ i : i + l ]
if t in vis :
return t
vis . add ( t )
return ''
n = len ( s )
left , right = 0 , n
ans = ''
while left < right :
mid = ( left + right + 1 ) >> 1
t = check ( mid )
ans = t or ans
if t :
left = mid
else :
right = mid - 1
return ans
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 class Solution {
private long [] p ;
private long [] h ;
public String longestDupSubstring ( String s ) {
int base = 131 ;
int n = s . length ();
p = new long [ n + 10 ] ;
h = new long [ n + 10 ] ;
p [ 0 ] = 1 ;
for ( int i = 0 ; i < n ; ++ i ) {
p [ i + 1 ] = p [ i ] * base ;
h [ i + 1 ] = h [ i ] * base + s . charAt ( i );
}
String ans = "" ;
int left = 0 , right = n ;
while ( left < right ) {
int mid = ( left + right + 1 ) >> 1 ;
String t = check ( s , mid );
if ( t . length () > 0 ) {
left = mid ;
ans = t ;
} else {
right = mid - 1 ;
}
}
return ans ;
}
private String check ( String s , int len ) {
int n = s . length ();
Set < Long > vis = new HashSet <> ();
for ( int i = 1 ; i + len - 1 <= n ; ++ i ) {
int j = i + len - 1 ;
long t = h [ j ] - h [ i - 1 ] * p [ j - i + 1 ] ;
if ( vis . contains ( t )) {
return s . substring ( i - 1 , j );
}
vis . add ( t );
}
return "" ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 typedef unsigned long long ULL ;
class Solution {
public :
ULL p [ 30010 ];
ULL h [ 30010 ];
string longestDupSubstring ( string s ) {
int base = 131 , n = s . size ();
p [ 0 ] = 1 ;
for ( int i = 0 ; i < n ; ++ i ) {
p [ i + 1 ] = p [ i ] * base ;
h [ i + 1 ] = h [ i ] * base + s [ i ];
}
int left = 0 , right = n ;
string ans = "" ;
while ( left < right ) {
int mid = ( left + right + 1 ) >> 1 ;
string t = check ( s , mid );
if ( t . empty ())
right = mid - 1 ;
else {
left = mid ;
ans = t ;
}
}
return ans ;
}
string check ( string & s , int len ) {
int n = s . size ();
unordered_set < ULL > vis ;
for ( int i = 1 ; i + len - 1 <= n ; ++ i ) {
int j = i + len - 1 ;
ULL t = h [ j ] - h [ i - 1 ] * p [ j - i + 1 ];
if ( vis . count ( t )) return s . substr ( i - 1 , len );
vis . insert ( t );
}
return "" ;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 func longestDupSubstring ( s string ) string {
base , n := 131 , len ( s )
p := make ([] int64 , n + 10 )
h := make ([] int64 , n + 10 )
p [ 0 ] = 1
for i := 0 ; i < n ; i ++ {
p [ i + 1 ] = p [ i ] * int64 ( base )
h [ i + 1 ] = h [ i ] * int64 ( base ) + int64 ( s [ i ])
}
check := func ( l int ) string {
vis := make ( map [ int64 ] bool )
for i := 1 ; i + l - 1 <= n ; i ++ {
j := i + l - 1
t := h [ j ] - h [ i - 1 ] * p [ j - i + 1 ]
if vis [ t ] {
return s [ i - 1 : j ]
}
vis [ t ] = true
}
return ""
}
left , right := 0 , n
ans := ""
for left < right {
mid := ( left + right + 1 ) >> 1
t := check ( mid )
if len ( t ) > 0 {
left = mid
ans = t
} else {
right = mid - 1
}
}
return ans
}
GitHub