Permutation in String

1.解析 

題目大意,判斷字符串s1(不考慮其順序)是否是s2的子串。

2.分析

這道題最簡單的辦法無非就是利用系統提供的next_permutation函數,該函數提供字符串排序的功能,這樣就可以不用考慮s1的順序,每次求解下一個排列,然後s2字符串直接判斷是否是其子串即可。這裏要特別注意next_permutation函數,如果不理解,可以具體查看它的源碼。但這種方法的時間複雜度很高,當s1字符串的長度超過20,進行全排序會耗掉大量的時間,更不用說100以上啦,所以肯定會TTL(時間超時)。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        if (s1.size() > s2.length()) return false;
        string org_s1 = s1;
        while (true){
            if (s2.find(s1) != string::npos) return true;  
            next_permutation(s1.begin(), s1.end()); //求解下一個排列
            if (org_s1 == s1) break;
        }
        return false;
    }
};

 上述方法行不通,一般求解連續子串使用滑動窗口無非是最好的。我們可以維持一個n大小的窗口,首先用hashtable統計字符串s1每個字符出現的個數(方便查找),然後,遍歷s2字符串,主要分爲以下兩種情況:

①s2字符串當前的字符不屬於s1字符串:重置窗口大小爲n,並重置hashtable

②s2字符串當前的字符屬於s1字符串:(1)匹配字符的個數已經爲0:向右滑動窗口,恢復待匹配的字符個數,直到碰到當前匹配的字符 (2)匹配字符的個數 > 0:當前窗口大小減1,待匹配的字符減1

具體實現如下:

class Solution {
public:
    bool checkInclusion(string s1, string s2){
        unordered_map<char, int> ch_to_val, c_ch_to_val;
        for (auto ch : s1) ch_to_val[ch]++;
        c_ch_to_val = ch_to_val;
        int n = s1.size(), m = s2.size(); //n爲滑動窗口大小
        bool flag = true;
        int start;
        for (int i = 0; i < m; ++i){
            if (ch_to_val.count(s2[i])){
                if (ch_to_val[s2[i]] > 0){
                    if (flag){
                        start = i; //記錄起始位置
                        flag = false;
                    }
                    if (--n <= 0) return true;
                    ch_to_val[s2[i]]--;
                }
                else{
                    for (; s2[start] != s2[i]; ++start){
                        n++;
                        ch_to_val[s2[start]]++; //重新計數
                    }
                    start++;
                }
            }
            else{ //重新匹配窗口
                ch_to_val.clear();
                ch_to_val = c_ch_to_val;
                n = s1.size();
                flag = true;
            }
        }
        
        return false;
    }
};

下面這種解法是對上面算法的優化,參考Grandyang博主的思路,其實我想複雜啦。用數組記錄s1字符串每個字符出現的個數,藉助左右指針,左指針代表窗口的左邊界,右指針代表窗口的右邊界。在遍歷字符串s2的過程中,如果當前字符在待匹配字符數組中,則判斷當前窗口的大小是否等於s1字符串的大小,如果等於,則滿足條件;若當前字符不在待匹配字符數組,則要麼是該字符不存在字符串s1要麼待匹配字符已經匹配完,則往左滑動窗口,並恢復之前匹配過的字符的個數,直到當前字符的計數爲0。之所以將數組大小設置爲123,主要是因爲字符'z'對應的ASCII爲122。具體實現如下:

class Solution {
public:
    bool checkInclusion(string s1, string s2){
        vector<int> val(123, 0);
        int n = s1.size(), left = 0; //left是左邊界
        for (auto ch : s1) val[ch]++;
        for (int right = 0; right < s2.size(); ++right){ //右邊界
            if (--val[s2[right]] < 0){
                while (++val[s2[left++]] != 0);
            }
            else if (right - left + 1 == n) return true; //判斷窗口大小
        }
        
        return false;
    }
};

下面這種解法也是來自Grandyang博主,使用雙數組,不借助雙指針。思路比較簡單,具體實現如下: 

class Solution {
public:
    bool checkInclusion(string s1, string s2){
        vector<int> m1(123, 0), m2(123, 0);
        int n1 = s1.size(), n2 = s2.size();
        for (int i = 0; i < n1; ++i){
            m1[s1[i]]++;
            if (i < n2) m2[s2[i]]++;
        }
        if (m1 == m2) return true;
        for (int i = n1; i < n2; ++i){
            ++m2[s2[i]];
            --m2[s2[i-n1]];
            if (m1 == m2) return true;
        }
        
        return false;
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章