题目要求:
给定一长字符串a和一短字符串b。请问,如何最快地判断出短字符串b中的所有字符是否都在长字符串a中?请编写函数实现此功能。
为简单起见,假设输入的字符串只包含大写英文字母。下面举几个例子。
1.如果字符串a是"ABCD",字符串b是"BAD",答案是true,因为字符串b中的字母都在字符串a中,或者说b是a的真子集。
2.如果字符串a是"ABCD",字符串b是"BCE",答案是false,因为字符串b中的字母E不在字符串a中。
3.如果字符串a是"ABCD",字符串b是"AA",答案是true,因为字符串b中的字母都在字符串a中。
为了以下说明方便,我们将a字符串长度设为n,b字符串长度设为m。
解法一:暴力查询
将b的每个字符串都到a中去查找一遍,查看是否存在。
时间复杂度O(nm),空间复杂度O(1)。
bool StringContain_way1(string &a,string &b)
{
for(int i=0;i<b.length();i++)
{
bool exist=false;
for(int j=0;j<a.length();j++)
{
if(a[j]==b[i])
{
exist=true;
break;
}
}
if(!exist)
{
return false;
}
}
return true;
}
解法二:排序后查询
先将原始字符串排序,再把b的字符串到a中去找,因为排过序,所以遍历a只要顺次遍历下去就行了。
c++sort用的是快速排序,时间复杂度是O(mlogm+nlogn),后面遍历时间复杂度是O(m+n),总时间复杂度是O(m+n+mlogm+nlogn),当n>>m时,总时间复杂度是(nlogn)。
空间复杂度O(1)。
bool StringContain_way2(string &a,string &b)
{
sort(a.begin(),a.end());
sort(b.begin(),b.end());
int i=0,j=0;
while(i<a.length() && j<b.length())
{
if(a[i]==b[j])
j++;
else if(a[i]<b[j])
i++;
else
return false;
if(i==a.length())
return false;
}
return true;
}
解法三:素数相乘
对于a,我们让每个字母都和一个素数对应,比如A对应2,B对应3,C对应5,以此类推。然后将a中对应的素数相乘得到一个乘积,称为a的素数积。
对于b,我们也使用相应的对应规则,每转化一个字母,就将a的素数积除以转化对应的素数,如果除不尽说明a没有这个字母,则退出;如果除得尽,说明a有这个字母,那接着转化b中的字母,直到转化完或者中途退出。
此方法时间复杂度O(m+n),当然最好的时候为O(n),就是b的第一个字母就不在a中。空间复杂度O(1)。
bool StringContain_way3(string &a,string &b)
{
const int prime[26]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101};
long long multi_a=1;
for(int i=0;i<a.length();i++)
{
int x=prime[a[i]-'A'];
if(multi_a%x)
multi_a*=x;
}
for(int i=0;i<b.length();i++)
{
int x=prime[b[i]-'A'];
if(multi_a%x)
return false;
}
return true;
}
这种方法看似可行,然而比较麻烦,因为前16个素数相乘的结果就已经超过long long能表示的范围了,所以如果想要用,结果得用字符串来存,当然得自己写一个字符串乘除法。
解法四:计数统计法
利用计数排序法的原理,因为字符串里只有A-Z这26个字母,我们可以定义两个26位的数组,然后遍历两个字符串,如果某个字母出现,就把这一位设置成1。判断错误条件是b的这一位为1,a的这一位为0。
此方法的时间复杂度为O(m+n),空间复杂度为O(1)。
bool StringContain_way4(string &a,string &b)
{
int p[26]={0};//记录a的字母
int q[26]={0};//记录b的字母
for(int i=0;i<a.length();i++)
p[a[i]-'A']=1;
for(int i=0;i<b.length();i++)
q[b[i]-'A']=1;
for(int i=0;i<26;i++)
{
if(q[i]==1 && p[i]==0)
return false;
}
return true;
}
解法五:哈希散列法
把a的所有字母存入哈希表中,然后遍历字符串b,看b中字母是否都在哈希表中。
此方法的时间复杂度为O(m+n),空间复杂度为O(n)。
因为C++使用哈希表的STL很麻烦,我这里就先用python写了。
Python中的dict的底层是依靠哈希表(Hash Table)进行实现的,使用开放地址法解决冲突。所以其查找的时间复杂度会是O(1)。
def StringContain_way5(a,b):
dict={}
for i in a:
dict[i]=1
for j in b:
if(not(j in dict)):
return False
return True
解法六:位运算法
其实在解法四的时候就能感觉到,申请一个int数组是多余的,应该申请bool数组。再进一步考虑,其实我们不需要数组,也不需要一整个哈希表,我们只需要一个数,取其中26位的0、1值来表示字母是否出现就行了。表示好之后在遍历b的时候只需要看对应位是否为1就行了。
此方法的时间复杂度为O(m+n),空间复杂度为O(1)。
bool StringContain_way6(string &a,string &b)
{
int flag=0;
for(int i=0;i<a.length();i++)
{
flag |=(1<<(a[i]-'A'));
}
for(int i=0;i<b.length();i++)
{
if((flag&(1<<(b[i]-'A')))==0)
return false;
}
return true;
}