题意:
给出两个长度为n的字符串A,B,各自找出一个子串使其能够匹配,输出最长匹配串长度。
匹配子串:其中各元素的种类和个数都相同。(一个串再排列后可变为另一串)
思考:
一开始想到的是二分,以为短的串匹配了,长的串才能匹配。其实不是这样。
比如:abcd与bdac,长度为4匹配了,但长度为3并不匹配。
当两个子串完全相等时是这样,但现在不考虑元素顺序,这种思路就不行了。
正解是哈希。
如果是朴素做法的话,应该是先遍历子串长度,再遍历A序列中子串开始的位置,再遍历B序列中子串开始的位置,再判断这两个子串是否匹配。时间复杂度为:n^3。
最朴素的地方是,遍历到A序列的一个开始的位置后,得遍历B序列所有开始的位置,这个的复杂都到n^2了,所以可以用hash优化这一步。
但是字符串哈希是判断相同的串的,这个再排列相同的串如何判断呢?
我们可以计算一个串中每个字母出现的次数(前缀和O(1)),然后将这个次数hash。这样,只要两个串中的字母出现的次数全部相同,那么两串的hash值就相同。
所以可以记录下A序列中所有开始位置时的子串hash,然后遍历B序列所有开始位置,判断当前子串的hash值是否在A序列中出现过。如果出现过,就说明这两个子串匹配。
这样,这一步的时间复杂度就为O(n)。
总的时间复杂度为O(n^2)。
实现:
将一个串按其中每个字母出现的次数哈希:
hash = 331;
sum += 'a'出现的次数;
sum * hash += 'b'出现的次数;
sum * hash += 'c'出现的次数;
...
Code:
const int N = 200010, mod = 1e9+7;
int T, n, m;
int pre1[N][30],pre2[N][30];
string a,b;
void pre()
{
for(int i=1;i<=n;i++){
for(int j=0;j<26;j++)
pre1[i][j]=pre1[i-1][j];
pre1[i][a[i]-'a']++;
}
for(int i=1;i<=m;i++){
for(int j=0;j<26;j++)
pre2[i][j]=pre2[i-1][j];
pre2[i][b[i]-'a']++;
}
}
int main(){
cin>>a>>b;
if(a.size()>b.size()) swap(a,b);
n=a.size(),m=b.size();
a=" "+a,b=" "+b;
pre();
map <ull, int> mp; //这里记得改成ull,wa了好几次
for(int len=n;len>=1;len--)
{
mp.clear();
ull hash=0,base=331;
for(int i=1;i<=n-len+1;i++)
{
int end=i+len-1;
hash=0;
for(int j=0;j<26;j++)
hash=hash*base+pre1[end][j]-pre1[i-1][j];
mp[hash]=1;
}
for(int i=1;i<=m-len+1;i++)
{
int end=i+len-1;
hash=0;
for(int j=0;j<26;j++)
hash=hash*base+pre2[end][j]-pre2[i-1][j];
if(mp.count(hash)){
cout<<len;return 0;
}
}
}
cout<<0;
return 0;
}
之后遇到字符串匹配问题都应该想到hash。