刷题的一天——滑动窗口

有一说一今天的题有点难。

1、给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例:

输入: s = "abcabcbb"
输出: 3 

这个题让人想到了双指针,但由于是字符串,所需要通过一定工具来记录双指针之间的字符,进而可以想到用哈希表的方式来完成。如果表中(也就是窗口中有重复,则先记录子串长度再逐渐删除重复元素以及之前的元素)。因此可以有以下代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set<char>u;
        int left=0,right=0,Max=0;
        int n=s.size();
        while(right<n){
            if(u.end()==u.find(s[right])){
                u.insert(s[right++]);                  //没有重复元素就插入hash表
                Max=max(right-left,Max);               //寻找最长子串数
            } else u.erase(s[left++]);                //左边窗口滑动到没有重复元素为止
        }
        return Max;                                     //等到右窗口到达边界就可以得到最大子串数量
    }
};

在时间复杂度上大概为O(n),空间复杂度由于有哈希表,所以是O(n),实质上应该和字符串内部的元素数量一致。

2、给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的 子串 。

示例:输入:s1 = "ab" s2 = "eidbaooo" 输出:true

输入:s1= "ab" s2 = "eidboaoo"输出:false

这个感觉同样可以用滑动窗口来解决,代码如下:
(1)滑动窗口法

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int n = s1.length(), m = s2.length();             //先检测下两者的长度
        if (n > m) {
            return false;
        }
        vector<int> cnt1(26), cnt2(26);
        for (int i = 0; i < n; ++i) {                       //设置一个定长的滑动窗口
            ++cnt1[s1[i] - 'a'];
            ++cnt2[s2[i] - 'a'];
        }
        if (cnt1 == cnt2) {                                  //如果相等就直接返回true
            return true;
        }
        for (int i = n; i < m; ++i) {                       //cnt2的窗口逐渐滑动,直到相等返回true或者不相等返回false
            ++cnt2[s2[i] - 'a'];
            --cnt2[s2[i - n] - 'a'];
            if (cnt1 == cnt2) {
                return true;
            }
        }
        return false;
    }
};

针对这个问题还有人给出了优化解法:

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int n = s1.length(), m = s2.length();
        if (n > m) {                               //先查看是否满足s1成为s2子数组的情况
            return false;
        }
        vector<int> cnt(26);
        for (int i = 0; i < n; ++i) {              //在cnt中不断加入s1元素,减去s2元素,留下的就是两个数组在前n个元素中不一样的元素。
            --cnt[s1[i] - 'a'];
            ++cnt[s2[i] - 'a'];
        }
        int diff = 0;
        for (int c: cnt) {                          //利用diff统计在前n个元素中不一样的元素
            if (c != 0) {
                ++diff;
            }
        }
        if (diff == 0) {                             //这里说明s1和s2前面n个元素恰好一样
            return true;
        }
        for (int i = n; i < m; ++i) {                 //这里就要陆续在cnt中添加+减少元素
            int x = s2[i] - 'a', y = s2[i - n] - 'a';   //窗口左右端的边界
            if (x == y) {                                //如果x和y一样的,对于diff没有影响
                continue;
            }
            if (cnt[x] == 0) {                          //被剔除的这一个元素是s1和s2共有(因为不存在于cnt),diff+1
                ++diff;                            
            }
            ++cnt[x];                                    //推进一步x
            if (cnt[x] == 0) {                           //推进之后的元素x不存在于cnt,所以是s1和s2共有,diff-1
                --diff;
            }
            if (cnt[y] == 0) {                           //和x同理
                ++diff;
            }
            --cnt[y];
            if (cnt[y] == 0) {
                --diff;
            }
            if (diff == 0) {                            //再次检验diff是否为0
                return true;
            }
        }
        return false;
    }
};

这一个优化的方法核心在于ent中的元素,通过判断s1与s2的相同/不同元素来实现。

时间复杂度:O(n+m+∣Σ∣),

空间复杂度:O(∣Σ∣)。

∣Σ∣代表字符集,也就是26个字母。

(2)双指针

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int n = s1.length(), m = s2.length();
        if (n > m) {                                   //排除s1比s2大的情况
            return false;
        }
        vector<int> cnt(26);                                
        for (int i = 0; i < n; ++i) {                 //减去cnt中的s1包含元素的权重 
            --cnt[s1[i] - 'a'];
        }
        int left = 0;
        for (int right = 0; right < m; ++right) {    //保证左右指针不溢出
            int x = s2[right] - 'a';                 
            ++cnt[x];                                //加入s2中右指针元素所在权重
            while (cnt[x] > 0) {                     //如果权重大于0说明不在s1中
                --cnt[s2[left] - 'a'];                //减去左边框字母的权重(左边框移动)
                ++left;
            }
            if (right - left + 1 == n) {               //如果有左右指针相隔n,说明存在最大子串
                return true;
            }
        }
        return false;
    }
};

时间复杂度:O(n+m+∣Σ∣)。

空间复杂度:O(∣Σ∣)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值