Hidden Anagrams(哈希,前缀和)

题目链接


题意:

给出两个长度为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。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值