1. 程式人生 > >【演算法】字串迴圈移位後是否包含

【演算法】字串迴圈移位後是否包含

問題

給定兩個字串s1和s2,要求判斷s2是否能夠被通過s1做迴圈移位(rotate)得到的字串包含。
例如,s1=AABCD和s2=CDAA,返回true;給定s1=ABCD和s2=ACBD,返回false。

解法一

最直接最笨的方法就對s1進行迴圈移動,再進行字串包含的判斷,從而遍歷其所有的可能性。字串迴圈移動,時間複雜度為O(n),字串包含判斷,採用普通的方法,時間複雜度為O(n*m),總體複雜度為O(n*n*m)。字串包含判斷,若採用KMP演算法,時間複雜度為O(n),這樣總體的複雜度為O(n*n)。若字串的長度n較大,顯然效率比較低。其中n為s1的長度,m為s2的長度。

解法二

對s1的迴圈移位操作,可以使用拼接s1+s1的方式得到。

以S1 = ABCD為例,先分析對S1進行迴圈移位之後的結果,如下所示:

ABCD—>BCDA—->CDAB—->DABC—->ABCD……

假設我們把前面的移走的資料進行保留,會發現有如下的規律:

ABCD—>ABCDA—->ABCDAB—->ABCDABC—->ABCDABCD……

因此,可以看出對s1做迴圈移位所得到的字串都將是字串s1s1的子字串。如果s2可以由s1迴圈移位得到,那麼s2一定在s1s1上,這樣時間複雜度就降低了。

/**
 * 解法二,拼接字串s1+s1
 * 如果s2可以由s1迴圈移位得到,那麼s2一定在s1s1上
 */
public static boolean isContainsByConcat(String a, String b){
    if(a == null || b == null || a.isEmpty() || b.isEmpty()) return false;

    String newStr = a.concat(a);
    return newStr.contains(b);
}

解法三

使用指標迴圈。我們的想法是,在s1後面”虛擬”地接上一個s1,這個”虛擬的s1”並不佔空間,但是仍然按照解法2的思路進行。那麼,如何實現這個”虛擬的s1”呢?其實只要把s1的最後一個元素,再指回s1的第一個元素即可。這可以用取模運算實現。比如,元素s1[(d1+i) mod d1]其實就是那個“虛擬的s1”的第i個元素,不會超出字串s1的長度。

用一個指標標記遍歷s1,最多兩遍,與s2中的第一個字元比對,如果命中則進入s2,進行逐位匹配,匹配失敗的話跳出s2繼續查詢s1。

/**
 * 解法三:指標迴圈查詢
 */
public static boolean isContainsByPointer(String a, String b){
    if(a == null || b == null || a.isEmpty() || b.isEmpty()) return false;

    int p = 0;  //s1的指標

    int aLen = a.length();
    int bLen = b.length();
    for(int i = 0; i < aLen * 2; i++){
        char c1 = a.charAt(p % aLen);

        //匹配到第一個字元相同後,遍歷s2字串
        if(c1 == b.charAt(0)){
            for(int j = 0; j < bLen; j++){
                if(a.charAt((p + j) % aLen) != b.charAt(j)){
                    break;  //本次不完全匹配,繼續往後
                }
            }
            return true;
        }
        p++;
    }
    return false;
}

優點:

  1. 字串長度較大時,效率依然較好;
  2. 不需要申請額外空間儲存第二個s1

參考資料