字典树
字符串
哈希函数
滚动哈希
题目描述
给你一个字符串 text
,请你返回满足下述条件的 不同 非空子字符串的数目:
可以写成某个字符串与其自身相连接的形式(即,可以写为 a + a
,其中 a
是某个字符串)。
例如,abcabc
就是 abc
和它自身连接形成的。
示例 1:
输入: text = "abcabcabc"
输出: 3
解释: 3 个子字符串分别为 "abcabc","bcabca" 和 "cabcab" 。
示例 2:
输入: text = "leetcodeleetcode"
输出: 2
解释: 2 个子字符串为 "ee" 和 "leetcodeleetcode" 。
提示:
1 <= text.length <= 2000
text
只包含小写英文字母。
解法
方法一:字符串哈希
字符串哈希 是把一个任意长度的字符串映射成一个非负整数,并且其冲突的概率几乎为 0。字符串哈希用于计算字符串哈希值,快速判断两个字符串是否相等。
取一固定值 BASE,把字符串看作是 BASE 进制数,并分配一个大于 0 的数值,代表每种字符。一般来说,我们分配的数值都远小于 BASE。例如,对于小写字母构成的字符串,可以令 a=1, b=2, ..., z=26。取一固定值 MOD,求出该 BASE 进制对 M 的余数,作为该字符串的 hash 值。
一般来说,取 BASE=131 或者 BASE=13331,此时 hash 值产生的冲突概率极低。只要两个字符串 hash 值相同,我们就认为两个字符串是相等的。通常 MOD 取 2^64,C++ 里,可以直接使用 unsigned long long 类型存储这个 hash 值,在计算时不处理算术溢出问题,产生溢出时相当于自动对 2^64 取模,这样可以避免低效取模运算。
除了在极特殊构造的数据上,上述 hash 算法很难产生冲突,一般情况下上述 hash 算法完全可以出现在题目的标准答案中。我们还可以多取一些恰当的 BASE 和 MOD 的值(例如大质数),多进行几组 hash 运算,当结果都相同时才认为原字符串相等,就更加难以构造出使这个 hash 产生错误的数据。
Python3 Java C++ Go Rust
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 distinctEchoSubstrings ( self , text : str ) -> int :
def get ( l , r ):
return ( h [ r ] - h [ l - 1 ] * p [ r - l + 1 ]) % mod
n = len ( text )
base = 131
mod = int ( 1e9 ) + 7
h = [ 0 ] * ( n + 10 )
p = [ 1 ] * ( n + 10 )
for i , c in enumerate ( text ):
t = ord ( c ) - ord ( 'a' ) + 1
h [ i + 1 ] = ( h [ i ] * base ) % mod + t
p [ i + 1 ] = ( p [ i ] * base ) % mod
vis = set ()
for i in range ( n - 1 ):
for j in range ( i + 1 , n , 2 ):
k = ( i + j ) >> 1
a = get ( i + 1 , k + 1 )
b = get ( k + 2 , j + 1 )
if a == b :
vis . add ( a )
return len ( vis )
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 class Solution {
private long [] h ;
private long [] p ;
public int distinctEchoSubstrings ( String text ) {
int n = text . length ();
int base = 131 ;
h = new long [ n + 10 ] ;
p = new long [ n + 10 ] ;
p [ 0 ] = 1 ;
for ( int i = 0 ; i < n ; ++ i ) {
int t = text . charAt ( i ) - 'a' + 1 ;
h [ i + 1 ] = h [ i ] * base + t ;
p [ i + 1 ] = p [ i ] * base ;
}
Set < Long > vis = new HashSet <> ();
for ( int i = 0 ; i < n - 1 ; ++ i ) {
for ( int j = i + 1 ; j < n ; j += 2 ) {
int k = ( i + j ) >> 1 ;
long a = get ( i + 1 , k + 1 );
long b = get ( k + 2 , j + 1 );
if ( a == b ) {
vis . add ( a );
}
}
}
return vis . size ();
}
private long get ( int i , int j ) {
return h [ j ] - h [ i - 1 ] * p [ j - i + 1 ] ;
}
}
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 typedef unsigned long long ull ;
class Solution {
public :
int distinctEchoSubstrings ( string text ) {
int n = text . size ();
int base = 131 ;
vector < ull > p ( n + 10 );
vector < ull > h ( n + 10 );
p [ 0 ] = 1 ;
for ( int i = 0 ; i < n ; ++ i ) {
int t = text [ i ] - 'a' + 1 ;
p [ i + 1 ] = p [ i ] * base ;
h [ i + 1 ] = h [ i ] * base + t ;
}
unordered_set < ull > vis ;
for ( int i = 0 ; i < n - 1 ; ++ i ) {
for ( int j = i + 1 ; j < n ; j += 2 ) {
int k = ( i + j ) >> 1 ;
ull a = get ( i + 1 , k + 1 , p , h );
ull b = get ( k + 2 , j + 1 , p , h );
if ( a == b ) vis . insert ( a );
}
}
return vis . size ();
}
ull get ( int l , int r , vector < ull >& p , vector < ull >& h ) {
return h [ r ] - h [ l - 1 ] * p [ r - l + 1 ];
}
};
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 func distinctEchoSubstrings ( text string ) int {
n := len ( text )
base := 131
h := make ([] int , n + 10 )
p := make ([] int , n + 10 )
p [ 0 ] = 1
for i , c := range text {
t := int ( c - 'a' ) + 1
p [ i + 1 ] = p [ i ] * base
h [ i + 1 ] = h [ i ] * base + t
}
get := func ( l , r int ) int {
return h [ r ] - h [ l - 1 ] * p [ r - l + 1 ]
}
vis := map [ int ] bool {}
for i := 0 ; i < n - 1 ; i ++ {
for j := i + 1 ; j < n ; j += 2 {
k := ( i + j ) >> 1
a , b := get ( i + 1 , k + 1 ), get ( k + 2 , j + 1 )
if a == b {
vis [ a ] = true
}
}
}
return len ( vis )
}
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 use std :: collections :: HashSet ;
const BASE : u64 = 131 ;
impl Solution {
#[allow(dead_code)]
pub fn distinct_echo_substrings ( text : String ) -> i32 {
let n = text . len ();
let mut vis : HashSet < u64 > = HashSet :: new ();
let mut base_vec : Vec < u64 > = vec! [ 1 ; n + 1 ];
let mut hash_vec : Vec < u64 > = vec! [ 0 ; n + 1 ];
// Initialize the base vector & hash vector
for i in 0 .. n {
let cur_char = (( text . chars (). nth ( i ). unwrap () as u8 ) - ( 'a' as u8 ) + 1 ) as u64 ;
// Update base vector
base_vec [ i + 1 ] = base_vec [ i ] * BASE ;
// Update hash vector
hash_vec [ i + 1 ] = hash_vec [ i ] * BASE + cur_char ;
}
// Traverse the text to find the result pair, using rolling hash
for i in 0 .. n - 1 {
for j in i + 1 .. n {
// Prevent overflow
let k = i + ( j - i ) / 2 ;
let left = Self :: get_hash ( i + 1 , k + 1 , & base_vec , & hash_vec );
let right = Self :: get_hash ( k + 2 , j + 1 , & base_vec , & hash_vec );
if left == right {
vis . insert ( left );
}
}
}
vis . len () as i32
}
#[allow(dead_code)]
fn get_hash ( start : usize , end : usize , base_vec : & Vec < u64 > , hash_vec : & Vec < u64 > ) -> u64 {
hash_vec [ end ] - hash_vec [ start - 1 ] * base_vec [ end - start + 1 ]
}
}
GitHub