leetcode 第 567 题:字符串的排列(C++)

567. 字符串的排列 - 力扣(LeetCode)

这题里字符串的排列的意思是s1中的字符可以交换顺序:

s1 = "ab"
s1的排列包括:"ab", "ba"

只有当两个字符串包含相同次数的相同字符时,一个字符串才是另一个字符串的排列

那就是要在s2中找到一个和s1一样长的子串,使得这个子串和s1是字母异位词(字符全相同,但是排列顺序不同):

字母异位词可以看这个:LeetCode第 242 题:有效的字母异位词(C++)_qq_32523711的博客-CSDN博客,不过这题里面不能用排序这种方法,会超时。

参照这个模板:

leetcode 第 76 题:最小覆盖子串(C++)_qq_32523711的博客-CSDN博客

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map need, window;
        for(const auto &c : s1) ++need[c];
        int left = 0, right = 0;
        int count = 0;
        while(right < s2.size()){
            auto c = s2[right];
            ++right;
            if(need.count(c) != 0){
                ++window[c];
                //说明字符c在window中已查全,目标是将need中所有字符查全,此时count == need.size()
                //代表窗口中的子串是s1的排列
                if(need[c] == window[c]) 
                    ++count;
            }
            //缩小窗口的时机是窗口大小等于s2.size()时
            //此时两者长度相等,如果每个字母出现频率也相等(count == need.size())
            //那么说明后者是前者的排列,返回true,否则我们从左边收缩窗口
            //这儿的while循环其实只会执行一次(不符合要求就增加左边界,那么循环条件自然不满足了)
            //所以可以改为if语句,判断条件改为==就行
            //while(right - left >= s1.size()){ 
            if(right - left == s1.size()){ //因为是排列,所以要与s2.size()比较
                if(count == need.size())    return true;
                auto d = s2[left];
                ++left;
                if(need.count(d)){
                	//注意window[d]的值是可能大于need[d]的值的
                	//比如"adc", "dcda"
                    if(window[d] == need[d])//两者数量刚好相等,移出之后肯定就不等
                        --count;
                    --window[d];
                }
            }
        }
        return false;
    }
};

上述代码用了两个哈希表,其实用一个(need)就可以了,滑动窗口的时候对need中key对应的值减1,直到减为0就说明该key查全了。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map need;
        for(const auto &c : s1) ++need[c];
        int left = 0, right = 0;
        int count = 0;
        while(right < s2.size()){
            auto c = s2[right];
            ++right;
            if(need.count(c) != 0){
                --need[c];
                if(need[c] == 0) 
                    ++count;
            }
            //缩小窗口的时机是窗口大小等于s2.size()时
            //此时两者长度相等,如果每个字母出现频率也相等(count == need.size())
            //那么说明后者是前者的排列,返回true,否则我们从左边收缩窗口
            if(right - left == s1.size()){ 
                if(count == need.size())    return true;
                auto d = s2[left];
                ++left;
                if(need.count(d)){//存在这个key
                    if(need[d] == 0)//这个key对应的数量刚好为0
                        --count;
                    ++need[d];
                }
            }
        }
        return false;
    }
};

哈希表改为数组也是可以的,因为目的只是计数而已,而且数组的计数以及查找之类操作更快,效率更高(执行用时会优秀很多)。

当然,思路还是一样的

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        vector need(26, 0), window(26,0); //小写字母26个
        for(const auto &c : s1) ++need[c-'a'];
        int target = 0; //记录s1中的不重复字符个数
        target = sum(need.begin(), need.end(), );
        for(const auto &c : need){
            if(c)   ++target;
        }
        int left = 0, right = 0;
        int count = 0;
        while(right < s2.size()){
            auto c = s2[right] - 'a';
            ++right;
            if(need[c] != 0){
                ++window[c];
                //说明字符c在window中已查全,目标是将need中所有字符查全
                if(need[c] == window[c]) 
                    ++count;
            }
            //缩小窗口的时机是窗口大小(子串长度)right-left == s2.size()时:
            //此时如果所有字母均查全(count == target)
            //那么说明子串是s1的排列,返回true,否则从左边收缩窗口,去掉一个元素
            if(right - left == s1.size()){ //因为是排列,所以子串与s1等长
                // 全部查全
                if(count == target)    return true;

                auto d = s2[left] - 'a';
                ++left;
                if(need[d]){
                    if(window[d] == need[d]) //两者数量刚好相等,移出之后肯定就不等
                        --count;
                    --window[d];
                }
            }
        }
        return false;
    }
};

上面我们用了两个数组,但是其实一个数组就可以(但是这样其实不太必要,一个数组反而会更麻烦):

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        vector need(26, 0); //小写字母26个
        for(const auto &c : s1) ++need[c-'a'];
        int target = 0; //记录s1中不重复字符个数,也即待查全字符个数
        for(auto &c : need){
            if(c)   ++target;
            else    c = INT_MIN; //把为0的元素全部变为INT_MIN(为了下面考虑)
        }
        int left = 0, right = 0;
        int count = 0;
        while(right < s2.size()){
            auto c = s2[right] - 'a';
            ++right;
            //说明该字符在s1中,就减少一个对该字符的需求
            //但是要注意,need[c]可能会减为负数,比如s1中只需要一个字符z,但是我们的
            //窗口中却有两个z(重复字符)的时候,这也是为什么上面会把s1中不存在元素在数组中对应的下标设置为INT_MIN的原因
            //当然,其实只要设为一个很小的负数(比如-20)就可以了,目的是为了区别开两类字符(在s1中的和不在s1中的)
            if(need[c] > INT_MIN){ 
                --need[c];
                //需求为0说明字符c已查全,目标是将need中所有字符查全
                if(need[c] == 0)  
                    ++count;
            }
            //缩小窗口的时机是窗口大小(子串长度)right-left == s2.size()时:
            //此时如果所有字母均查全(count == target)
            //那么说明子串是s1的排列,返回true,否则从左边收缩窗口,去掉一个元素
            if(right - left == s1.size()){ //因为是排列,所以子串与s1等长
                // 全部查全
                if(count == target)    return true;

                auto d = s2[left] - 'a';
                ++left;
                if(need[d] > INT_MIN){ 
                    //如果为0说明供需平衡,把它去掉必然供给不足,那么查全数应该减去1
                    //其他情况都不能改变count的值
                    if(need[d] == 0) 
                        --count;
                    ++need[d]; //移出之后需求肯定加1
                }
            }
        }
        return false;
    }
};

你可能感兴趣的:(leetcode,leetcode,字符串,滑动窗口)