編程之法1.2字符串的包含

題目要求:

  給定一長字符串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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章