1. 程式人生 > >程式設計之法面試和演算法心得-1.2字串的包含

程式設計之法面試和演算法心得-1.2字串的包含

一、題目描述

給定一個長字串a和一段字串b。請問,如何最快判斷短字串b中的所有字元是否都包含在a中?請編寫StringContain(a, b)實現此功能。為簡單講明思想,假設輸入的字串都是大寫的字母。如a=“ABCD”,b=“BAD”,則答案為True;a=“ABCD”,b=“BCE”,則答案為False。

二、解法一:蠻力輪詢

可以將b中的字元逐個查詢是否在a中,但是該法的時間複雜度太高,為O(mn)。實現演算法如下:

def StringContain(a, b):
    a = list(a)
    b = list(b)
    count = 0
    for i in range(len(a)):
        for j in range(len(b)):
            if(b[j] != a[i]):
                count = count + 1
        if (count == len(b)):
            return False
    return True

三、解法二:排序後在查詢

如果先拍好訓,再進行蠻力查詢,需要的時間複雜度比解法一有所降低,為O(m+n),只需要將a和b字串都逐步輪詢一遍即可。但不要忘記了還有一個排序複雜度,用自帶的函式即可,時間複雜度為:O(mlog(m)+nlog(n))。
最終的參考程式碼如下:

def StringContain(a, b):
    a = list(a)
    b = list(b)
    a = sorted(a)
    b = sorted(b)
    pa = 0
    pb = 0
    while (pb < len(b)):
        while((pa < len(a)) & (a[pa] < b[pb])):
            pa = pa + 1
        if((pa >= len(a)) | (a[pa] >b[pb])):
            return False
        pb = pb + 1
    return True

四、解法三:素數相乘

素數相乘的思路是將素數代替字母,因為素數具有被本身和1整除的性質,因此可以利用該性質判斷b中的字元是否在a中,時間複雜度為:O(m+n)。具體的解法思路如下:

  1. 建立一個list,將從小到大的順序將素數編排到列表中,然後用程式將對應的字元換成素數;
  2. 遍歷a字串,得到a字元的素數相乘的結果f;
  3. 遍歷b字串,將第2步得到的結果除以b中每一個字元對應的素數,如果能整除,則b的該字元在a中。

具體程式碼如下:

def StringContain(a, b):
    p = [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]
    f = 1
    for i in range(len(a)):
        x = p[ord(a[i]) - ord('A')]
        if (f % x):  ## 同樣的字元對應的素數不要重複乘,防止增加無效的資料量
            f = f * x
    for i in range(len(b)):
        x = p[ord(b[i]) - ord('A')]
        if(f % x):
            return False
    return True

五、解法四:位運演算法

位運演算法比較巧妙,學過微機原理或者計算機原理可能好理解一點,這裡用到了移位的知識,通過移位後的結果來判斷b中是否具有該字元。時間複雜度為:O(m+n)。
具體實現程式碼如下:

def StringContain(a, b):
    hash = 0
    for i in range(len(a)):
        hash = hash | (1 << (ord(a[i])-ord('A')))
    for i in range(len(b)):
        if( (hash & (1 << (ord(b[i])-ord('A')))) == 0 ):
            return False
    return True

ord()是python中將字元換成ascii碼的一個內建操作。反之為chr(),將ascii碼換成字元。
作者在書中說這還不是最完美的,還有更完美的方法,歡迎讀者在下方留言討論。

六、課後題:變位詞

題目:如果兩個字串中的字元一樣,出現的次數也一樣,只是出現的順序不一樣,則認為這兩個字串是兄弟字串。如“bad”和“abd”,即為兄弟字串。
為了簡單說明,假設輸入的都是大寫字母。直接利用​位運演算法,可以檢查一樣的字元,但是檢查不出字元的個數,如abc和abcc。
改進:建立一個list列表,每一個字元用一個單獨的位運演算法,加入到list中,最後將list中的元素相加,判斷對應的十進位制是否相等。但是這個還有其他情況,比如兩個不一樣的字元剛好相加是相等的,因此還要用位運演算法去判斷裡面的字元是否相等。
輪尋和排序後輪尋都可以解決這個問題。第一步都是判斷一字元長度是否相等,相等在繼續下面操作。直接輪尋每找到一個對應的字元,雙方都要刪除該字元,直到最後一個字元。排序後輪尋需要雙方的都加一,一起指向對應的下一個字元。
位運演算法具體實現如下:

def StringContain(a, b):
    hash = 0
    hasha = 0
    hashb = 0
    for i in range(len(a)):
        hash = hash | (1 << (ord(a[i]) - ord('A')))
        hasha = hasha + (1 << (ord(a[i])-ord('A')))
    for i in range(len(b)):
        hashb = hashb + (1 << (ord(b[i])-ord('A')))
        if( (hash & (1 << (ord(b[i])-ord('A')))) == 0 ):
            return False
    print(hasha)
    print(hashb)
    if(hasha == hashb):
        return True
    else:
        return False

整個程式碼地址:https://github.com/idotc/Interview-And-Algorithm-Experience/tree/master/第二章