【优选算法之滑动窗口】No.4--- 经典滑动窗口算法(下)


前言

在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:优选算法
🔑本章内容:滑动窗口
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


提示:以下是本篇文章正文内容,下面案例可供参考

一、滑动窗口示例:

1.1 ⽔果成篮

  1. 题⽬链接:904. ⽔果成篮

  2. 题⽬描述:
    在这里插入图片描述

  3. 解法(滑动窗⼝):
    算法思路:研究的对象是⼀段连续的区间,可以使⽤「滑动窗⼝」思想来解决问题。让滑动窗⼝满⾜:窗⼝内⽔果的种类只有两种。

  4. 做法:右端⽔果进⼊窗⼝的时候,⽤哈希表统计这个⽔果的频次。这个⽔果进来后,判断哈希表的⼤⼩:

  • 如果⼤⼩超过 2:说明窗⼝内⽔果种类超过了两种。那么就从左侧开始依次将⽔果划出窗⼝,直到哈希表的⼤⼩⼩于等于 2,然后更新结果;
  • 如果没有超过 2,说明当前窗⼝内⽔果的种类不超过两种,直接更新结果 ret。
  1. 算法流程:
  • 初始化哈希表 hash 来统计窗⼝内⽔果的种类和数量;
  • 初始化变量:左右指针 left = 0,right = 0,记录结果的变量 ret = 0;
  • 当 right ⼩于数组⼤⼩的时候,⼀直执⾏下列循环:
    <1> 将当前⽔果放⼊哈希表中;
    <2> 判断当前⽔果进来后,哈希表的⼤⼩: 如果超过 2:
    ◦ 将左侧元素滑出窗⼝,并且在哈希表中将该元素的频次减⼀;
    ◦ 如果这个元素的频次减⼀之后变成了 0,就把该元素从哈希表中删除;
    ◦ 重复上述两个过程,直到哈希表中的⼤⼩不超过 2;
    <3> 更新结果 ;
    <4> right++,让下⼀个元素进⼊窗⼝;
  • 循环结束后, 存的就是最终结果
  1. C++代码(数组模拟哈希/容器)
class Solution {
public:
    //const int N=1e5+10;
    int totalFruit(vector<int>& fruits) 
    {
        int cnt=0;
        //int hash[N];
        //memset(hash,0,sizeof(hash));
        unordered_map<int,int>hash;
        int left,right,ans=0;
        for(left=0,right=0;right<fruits.size();right++)
        {
            // if(hash[fruits[right]]==0)
            // {
            //     cnt++;
            // }
            hash[fruits[right]]++;//进窗口
            //while(cnt>2)
            while(hash.size()>2)//判断
            {
                if(--hash[fruits[left]]==0)
                {
                    //cnt--;
                    hash.erase(fruits[left]);
                }
                left++;//出窗口
            }
            ans=max(ans,right-left+1);//更新结果
        }
        return ans;
    }
};

1.2 找到字符串中所有字⺟异位词

  1. 题⽬链接:438. 找到字符串中所有字⺟异位词
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法(滑动窗⼝ + 哈希表):
    算法思路:注意分清cnt代表的是什么,right-left+1代表的是什么 ,还有p.size()
  • 因为字符串 p 的异位词的⻓度⼀定与字符串 p 的⻓度相同,所以我们可以在字符串 s 中构造⼀个⻓度为与字符串 p 的⻓度相同的滑动窗⼝,并在滑动中维护窗⼝中每种字⺟的数量;
  • 当窗⼝中每种字⺟的数量与字符串 p 中每种字⺟的数量相同时,则说明当前窗⼝为字符串 p 的异位词;
  • 因此可以⽤两个⼤⼩为 26 的数组来模拟哈希表,⼀个来保存 s 中的⼦串每个字符出现的个数,另⼀个来保存 p 中每⼀个字符出现的个数。这样就能判断两个串是否是异位词。
  1. C++代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) 
    {
        int hash1[26]={0},hash2[26]={0};
        vector<int> v;

        for(auto&e:p)
        hash1[e-'a']++;

        int left,right,cnt=0;//cnt代表窗口中有效值的个数
        for(left=0,right=0;right<s.size();right++)
        {
            char in=s[right];
            //这里是每个字符都进入窗口但是不存在于p中的字符++会大于hash1中该字符的数量所以只有<=才将有效值cnt++
            if(++hash2[in-'a']<=hash1[in-'a'])cnt++;//进窗口+(有效值++)
            if(right-left+1>p.size())//窗口大小大于p的个数就要出窗口
            {
                char out=s[left++];
                //这条语句代表的是:当出窗口的字符个数<=has1中该字符的个数时,
                //就代表出的是一个有效字符然后将该字符--,窗口中有效值count--
                if(hash2[out-'a']--<=hash1[out-'a'])cnt--;
            }
            if(cnt==p.size())//更新结果
            v.push_back(left);
        }
        return v;
    }
};

1.3 串联所有单词的⼦串

  1. 题⽬链接:30. 串联所有单词的⼦串
  2. 题⽬描述:
    在这里插入图片描述
  3. 算法思路:这道题和上一道类似只不过是字符换成了字符串
    如果我们把每⼀个单词看成⼀个⼀个字⺟,问题就变成了找到「字符串中所有的字⺟异位词」。⽆⾮就是之前处理的对象是⼀个⼀个的字符,我们这⾥处理的对象是⼀个⼀个的单词
  4. C++代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        int n = words.size(), m = words[0].size();
        int left, right,cnt = 0;
        unordered_map<string, int> hash1, hash2;
        for (auto& e : words)
            hash1[e]++;
        vector<int> v;
        for(int i=0;i<m;i++)//这里刚开始没有加理解错了以为s都是和words相同长度的字符串
        {
            hash2.clear();cnt=0;
            for (left = i, right = i; right+m <= s.size(); right+=m) 
            {
                string instr=s.substr(right,m);
                if (++hash2[instr] <= hash1[instr])cnt++;
                if ((right - left + 1) > n*m) 
                //这里不能用/因为right-left+1并不一定是一个可以除尽m的数就像7/3和2比本来该出窗口但是2==2就跳过了
                {
                    string outstr = s.substr(left, m);
                    left += m;
                    if (hash2[outstr]-- <= hash1[outstr])cnt--;
                }
                if (cnt == n)
                    v.push_back(left);
            }
        }
        return v;
    }
};

1.4 最⼩覆盖⼦串

  1. 题⽬链接:76. 最⼩覆盖⼦串
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法(滑动窗⼝ + 哈希表):
    算法思路:
  • 研究对象是连续的区间,因此可以尝试使⽤滑动窗⼝的思想来解决。
  • 如何判断当前窗⼝内的所有字符是符合要求的呢?
    我们可以使⽤两个哈希表,其中⼀个将⽬标串的信息统计起来,另⼀个哈希表动态的维护窗⼝内字符串的信息。当动态哈希表中包含⽬标串中所有的字符,并且对应的个数都不⼩于⽬标串的哈希表中各个字符的个数,那么当前的窗⼝就是⼀种可⾏的⽅案。
  1. 算法流程:
  • 定义两个全局的哈希表:
  • 先将 t 的信息放⼊ 1 号哈希表中;
  • 初始化⼀些变量:左右指针: left = 0,right = 0 ;⽬标⼦串的⻓度: ans=INT_MAX ;⽬标⼦串的起始位置: begin ;(通过⽬标⼦串的起始位置和⻓度,我们就能找到结果)
  • 当 right ⼩于字符串 s 的⻓度时,⼀直下列循环:
    <1> 将当前遍历到的元素扔进 2 号哈希表中;
    <2> 检测当前窗⼝是否满⾜条件:
    如果满⾜条件:
    ◦ 判断当前窗⼝是否变⼩。如果变⼩:更新⻓度 ans,以及字符串的起始位置 begin;
    ◦ 判断完毕后,将左侧元素滑出窗⼝,顺便更新 2 号哈希表;
  1. C++代码
class Solution {
public:
    string minWindow(string s, string t) 
    {
        unordered_map<char,int>hash1,hash2;
        int left,right,cnt=0;//cnt代表字符串中有效种类数
        int ans=INT_MAX,begin=-1;
        //set去重求t的种类数
        set<char> st;
        for(auto&e:t)st.insert(e);

        for(auto&e:t)
        hash1[e]++;
        for(left=0,right=0;right<s.size();right++)
        {
            char instr=s[right];
            if(++hash2[instr]==hash1[instr])cnt++;
            while(cnt==st.size())
            {
                if(ans>right-left+1)
                {
                    ans=right-left+1;
                    begin=left;
                }
                if(hash2[s[left]]--==hash1[s[left]])cnt--;
                left++;
            }
        }
        return begin==-1?"":s.substr(begin,ans);
    }
};

二、滑动窗口总结:

  • 根据上面几道例题可以看出滑动窗口大多和hash表配合使用除此之外还要注意使用滑动窗口的的条件:一定是连续的区间才可以使用滑动窗口
  • 除此之外,还要注意滑动窗口更新结果的位置具体题目具体分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小沈YO.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值