題目要求:
給定一長字符串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;
}