1. 程式人生 > 實用技巧 >Leetcode上兩道很有意思的字串題(都可以利用重複拼接自身快速解答)

Leetcode上兩道很有意思的字串題(都可以利用重複拼接自身快速解答)

今天做了Leetcode上一道簡單題,一開始我想用兩個指標一次迴圈的方法來做,結果怎麼都通過不了,無奈看了答案,答案的方法非常巧妙,通過拼接自身字串發現潛在的特性。

這種題目我已經發現了兩道,一起來看看這兩道題。

459.重複的子字串

給定一個非空的字串,判斷它是否可以由它的一個子串重複多次構成。給定的字串只含有小寫英文字母,並且長度不超過10000。

示例 1:

輸入: "abab"

輸出: True

解釋: 可由子字串 "ab" 重複兩次構成。
示例 2:

輸入: "aba"

輸出: False
示例 3:

輸入: "abcabcabcabc"

輸出: True

解釋: 可由子字串 "abc" 重複四次構成。 (或者子字串 "abcabc" 重複兩次構成。)

這道題的第一想法就是暴力搜,遍歷第1到第n/2個子字串,設從0開始,長度為x,對每個子字串都遍歷剩下的(n/x)-1個字串,按順序剩下的每一個字元s[i]都應該和s[i-x]相同,所以再迴圈一個總字串的長度即可。時空複雜度較高。

除了這種暴力解法之外,Leetcode官方提供了一個非常簡潔有效的解法(不是KMP,我也不會KMP) 。先給出答案的連結:

https://leetcode-cn.com/problems/repeated-substring-pattern/solution/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/

答案通過重複拼接自身構成(s,s)字串,如果該字串是由重複的子字串組成的,那麼去頭掐尾以後,形成的新字串(s,s)[1:-1]中一定包含原來的字串s,讀者可以自己試試,這裡就不給出證明了,官方答案不僅證明了充分性也證明了必要性。也就是說,我們構造一個(s,s)字串後,再掐頭去尾得到(s,s)[1:-1],在這個字串中找s,如果找不著,那麼說明這個字串應該不是重複子字串得到的。程式碼的邏輯是,找(s,s)[1:]中的s,如果找到的是最後一個末尾的s,那麼就不是重複子字串。

貼個程式碼:

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        return (s+s).find(s, 1) != s.size();
    }
};

  巧合的是,我前幾天也做了一題——字串輪轉,這道題是一道面試題。同樣也是使用拼接自身構造了一個新的字串再去判斷。先看題目:

面試題01.09 字串輪轉

字串輪轉。給定兩個字串s1和s2,請編寫程式碼檢查s2是否為s1旋轉而成(比如,waterbottle是erbottlewat旋轉後的字串)。

示例1:

輸入:s1 = "waterbottle", s2 = "erbottlewat"


輸出:True
示例2:

輸入:s1 = "aa", s2 = "aba"
輸出:False
提示:

字串長度在[0, 100000]範圍內。
說明:

你能只調用一次檢查子串的方法嗎?

同樣的,當我們拿到這道題,第一思路就是使用一次迴圈區構造所有的字串輪轉後的結果S1,S2...Sn,同時比較s2是否與之相同,如果有一個相同說明s2是s1旋轉得到的。但是題目要求我們只使用一次檢查子串的方法。

做法很簡單,如果s2是s1旋轉得到的,重複拼接s1得到(s1,s1)中一定包含s2,相當於是把所有旋轉的結果都放進了一個字串中。那麼程式碼就很簡單了。

class Solution {
    public:
    bool isFlipedString(string s1, string s2) {
        if (s1.size() != s2.size()) return false;
        return (s2 + s2).rfind(s1) != -1;
    }
};

  我在這裡用的是C++中rfind的方法,當然直接使用find也是可以的。

總結

兩道題中的字串,第一個是重複子字串,第二個是旋轉得到的字串,都具備類似的性質,

當重複子字串得到的字串s拼接自身(s,s)後,即便掐頭去尾,s也在(s,s)[1:-1]中,

當字串s拼接自身(s,s)後,由它旋轉得到的所有字串s1,s2...Sn,都在(s,s)中,

所以兩道題都利用了這個性質,寫出了簡潔有效的程式碼。