欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Permutation in String

程序员文章站 2022-03-21 21:21:25
...

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;
    }
};

 

相关标签: # 滑动窗口