C++算法学习专题:滑动窗口

本篇博客我们将深入学习经典的算法思想:滑动窗口

相关的习题博客已经上传至作者的个人gitee:CPP 学习代码库: C++代码库新库,旧有C++仓库满员了,喜欢请支持一下谢谢。

目录

什么是滑动窗口算法

滑动窗口的应用场景

滑动窗口的实现步骤

滑动窗口的变种

1、长度最小的子数组

2、无重复字符的最长字串

3、最大连续1的个数三

[4、将x减到0 的最小操作数](#4、将x减到0 的最小操作数)

5、水果成篮

6、找到字符串中所有的字符异位词

7、串联所有单词的字串

8、最小覆盖字串


什么是滑动窗口算法

滑动窗口(Sliding Window)是一种经典的双指针算法思想,主要用于处理数组或链表的连续子序列问题。它通过维护一个窗口区间来高效地解决问题,避免了不必要的重复计算。

滑动窗口的基本思想是:维护一个窗口,通过移动窗口的边界来遍历数据。窗口的大小可以是固定的(固定窗口),也可以是变化的(动态窗口)。

滑动窗口的应用场景

滑动窗口算法特别适用于以下类型的问题:

  1. 子串/子数组问题:如"查找字符串中无重复字符的最长子串"
  2. 连续元素问题:如"寻找和大于等于目标值的最短连续子数组"
  3. 统计类问题:如"统计数组中满足特定条件的连续子数组个数"

滑动窗口的实现步骤

典型的滑动窗口算法实现步骤如下:

  1. 初始化窗口边界(通常是left=right=0)
  2. 扩展窗口(移动right指针)
  3. 当窗口满足条件时,收缩窗口(移动left指针)
  4. 在窗口移动过程中记录或更新结果

滑动窗口的变种

  1. 固定窗口:窗口大小保持不变,如"大小为K的子数组的最大平均数"
  2. 计数窗口:窗口大小由元素计数决定,如"包含最多K个不同字符的最长子串"
  3. 多指针窗口:使用多个指针维护复杂窗口条件

1、长度最小的子数组

思路:

解法一:暴力枚举所有子数组的和O(N^3)

解法二:利用单调性,使用同向双指针优化算法O(N^2)

揭发三:滑动窗口:

解法思路:

1、left、right=0;

2、进窗口

3、判断

4、出窗口。

其中2、3为循环操作

滑动窗口的正确性:利用了单调性规避了无效的枚举

答案:

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int left=0,right=0;
        int n=nums.size();
        int len=INT_MAX;
        int sum=0;
        while(right<n)
        {
            sum+=nums[right];    
            while(sum>=target)
            {
                len=min(len,right-left+1);
                sum-=nums[left++];
            }
            right++;

        }
        return len==INT_MAX?0:len;
    }
};

2、无重复字符的最长字串

字串可以认为是子数组

解题思路1:暴力枚举+哈希表(判断是否有字符重复)

解题思路2:"滑动窗口"来解决问题

思路:

1、left=0,fight=0;

2、进窗口------字符进入哈希表

3、判断是否出现重复字符,如果出现就出哈希表(删除字符)

4、更新结果

答案:

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int n=s.size();
        int hash[128]={0};//用数组模拟哈希表
        int left=0,right=0;
        while(right<n)
        {
            hash[s[right]]++;//进入哈希表就是进入窗口
            while(hash[s[right]]>1)//判断,>1出窗口
            {
                hash[s[left]]--;//出窗口
            }
            ret=max(ret,right-left+1);
            right++;//下一个元素进窗口
        }

    }
};

3、最大连续1的个数三

解决思路:等价为在数组中找到最长子数组,满足0的个数小于k即可

算法一:暴力解法所有的子数组,再统计0的个数。

算法二:利用滑动窗口。

1、left=0,right=0;

2、进窗口如果是1无视,如果是0进窗口

3、判断.zero>k则非法。

cpp 复制代码
class Solution {
public:
    int longestOnes(vector<int>& nums, int k)
    {

        int ret = 0;
        for (int left = 0, right = 0, zero = 0; right < nums.size(); right++)
        {
            if (nums[right] == 0)
            {//进窗口
                zero++;
            }
            while (zero > k)
                if (nums[left++] == 0)
                    zero--;//出窗口
            ret = max(ret, right - left + 1);//更新结果

        }
        return ret;

    }
};

4、将x减到0 的最小操作数

算法思路:左右各选择一个区间,使中间元素相加为x。转化为找到最长的子数组长度。

滑动窗口:初始化left、right、sum为0 ,target=sum-x;进窗口,sum+nums[right];判断,sum>target的时候出窗口,即sum-nums[left];当sum=target时,更新结果

cpp 复制代码
class Solution {
public:
    int minOperations(vector<int>& nums, int x)
    {
        int left = 0, right = 0, tmp = 0;
        int sum = 0;

        for (auto a : nums) sum += a;
        int target = sum - x;
        //找不到
        if (target < 0) return -1;
        int ret = -1;
        //滑动窗口
        while (right < nums.size())
        {

            //进窗口
            tmp += nums[right];
            //判断
            while (tmp > target)
                tmp -= nums[left++];
            if (target == tmp)//更新结果
                ret = max(ret, right - left + 1);
            right++;
        }
        return ret == -1 ? ret : nums.size() - ret;
    }
};

5、水果成篮

转化为:找出最长子数组的长度,子数组中不超过两种类型的水果

算法一:暴力解法+哈希表

算法二:滑动窗口+哈希表

在left和right之间种类只有两种变化:不变或者变小。种类不变的时候right不变;种类变小的时候right向右。

滑动窗口:

1、初始化left、right为0

2、进窗口:将相同元素填入哈希表,+1

3、出窗口:哈希表的长度大于2,此时出窗口,left++

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) 
    {//算法一
        //unordered_map<int,int> hash;//统计窗口内多少钟水果
        // int left=0,right=0;
        // int ret=0;
        // while(right<fruits.size())
        // {
        //     hash[fruits[right]]++;//进窗口
        //     while(hash.size()>2)//出窗口
        //     {
        //         hash[fruits[left]]--;
        //         if(hash[fruits[left]]==0)
        //             hash.erase(fruits[left]);
        //         left++;
        //     }
        //     ret=max(ret,right-left+1);
        //     right++;
        // }
        // return ret;
        //算法二:数组模拟哈希表
        int hash[100001]={0};
        int left=0,right=0;
        int kinds=0;//水果种类
        int ret=0;
        while(right<fruits.size())
        {
            if(hash[fruits[right]]==0) kinds++;//维护水果种类
            hash[fruits[right]]++;//进窗口
            while(kinds>2)//出窗口
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]]==0)
                    kinds--;
                left++;
            }
            ret=max(ret,right-left+1);
            right++;
        }
        return ret;
    }
};

6、找到字符串中所有的字符异位词

算法思路:滑动窗口+哈希表

如果快速判断两个字符串是否为异位词------哈希表

利用哈希表遍历字符串一,将每个字符出现次数记录,再遍历字符串二,出现相同字符的时候对应减去1,如果为0则证明为异位词。

利用变量count统计窗口中"有效字符"的个数优化

滑动窗口:

1、初始化left和right为0。准备两个哈希表hash1、hash2分别对应两个字符

2、进窗口。如果hash2[in]<=hash1[in],那么count++

3、判断出窗口。hash2[out]<hash1[out],则是有效字符,count--;其余情况下不用管count

4、更新结果:检查hash1和hash2的结果是否一致。

cpp 复制代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int>ret;
        int hash1[26]={0};//统计p中每个字符个数
        int hash2[26]={0};//统计款口中字符出现的个数
        for(auto& ch:p) hash1[ch-'a']++;
        int left=0,right=0,count=0;
        int m=p.size();
        while(right<s.size())
        {
            char in=s[right];//进入窗口字符
            hash2[in-'a']++;//进入窗口
            if(hash2[in-'a']<=hash1[in-'a']) count++;
            if(right-left+1>m) 
            {
                char out =s[left++];
                //出窗口
                if(hash2[out-'a']<=hash1[out-'a']) count--;
                hash2[out-'a']--;
            }
            //更新结果
            if(count==m)
                ret.push_back(left);
            right++;
        }
        return ret;
    }
};

7、串联所有单词的字串

这道题的思路与上一题类似,可以将words中的词当作一个字符对待。

区别:

1、哈希表

用hash<string ,int>

2、left、right

两者移动的步长是每个单词的长度

3、滑动窗口次数

cpp 复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        unordered_map<string,int>hash1;//保存words中所有单词频次
        vector<int> ret;
        for(auto& e:words) hash1[e]++;
        int len=words[0].size(),m=words.size();
        //滑动窗口
        for(int i=0;i<len;i++)//执行len次滑动窗口
        {//count为有效字符串个数
            unordered_map<string,int>hash2;//维护窗口内单词的频次
            for(int left=i,right=i,count=0;right+len<=s.size();right+=len)
            {
                
                //进窗口
                string in=s.substr(right,len);//
                hash2[in]++;
                //判断in是否在里面
                if(hash1.count(in)&&hash2[in]<=hash1[in]) count++;
                //判断
                if(right-left+1>m*len)
                {
                    //出窗口
                    string out=s.substr(left,len);
                    //判断out是否在里面
                    if(hash1.count(out)&&hash2[out]<=hash1[out]) count--;
                    hash2[out]--;
                    left+=len;
                }
                //更新维护
                if(count==m) ret.push_back(left);
            }
        }
        return ret;
    }
};

8、最小覆盖字串

算法思路:滑动窗口+哈希表

准备两个哈希表hash1和hash2,分别给s和t准备

利用count记录有效字符的种类

滑动窗口:

1、初始化hash1、hash2,left、right为0

2、进窗口:当hash2[in]与hash1[in]相等时,count++

3、进窗口:当hash2[out]与hash1[out]相等时,count--

4、更新结果:起始位置,最短长度

cpp 复制代码
class Solution {
public:
    string minWindow(string s, string t) 
    {
       //容器相比于数组模拟开销比较大
       int hash1[128]={0}; //统计字符串t中每一个字符的频次 
       int kinds=0;//统计有多少种有效字符
       for(auto& e:t) 
       {
        if(hash1[e]==0) kinds++;
        hash1[e]++;
       }
       int hash2[128]={0};//统计窗口内每一个字符的频次
       int minlen=INT_MAX,begin=-1;//最短长度
       for(int left=0,right=0,count=0;right<s.size();right++)
       {
            char in=s[right];
            hash2[in]++;
            if(hash2[in]==hash1[in]) count++;//进窗口+维护count
            //判断
            while(kinds==count)
            {
                //更新结果
                if(right-left+1<minlen)
                {
                    minlen=right-left+1;
                    begin=left;
                }
                //  
                char out=s[left++];
                if(hash2[out]==hash1[out]) count--;
                hash2[out]--;
            }
            
       }
       if(begin==-1) return "";
       else return s.substr(begin,minlen);
      
    }
};

本期滑动窗口的内容就此结束。喜欢请点个赞支持一下,谢谢

相关推荐
摸鱼一级选手3 分钟前
十大经典 Java 算法解析与应用
java·算法·排序算法
海鸥_34 分钟前
C++中不加{}导致的BUG
c++·bug
MowenPan19951 小时前
高等数学 9.1多元函数的基本概念
笔记·学习·高等数学
Warren981 小时前
如何在 Spring Boot 中安全读取账号密码等
java·开发语言·spring boot·后端·安全·面试·测试用例
燃尽了,可无1 小时前
C#基础编程核心知识点总结
开发语言·c#
Ldawn_AI2 小时前
4+ 图论高级算法
算法·深度优先·图论
Xの哲學2 小时前
Linux PCI 子系统:工作原理与实现机制深度分析
linux·网络·算法·架构·边缘计算
llrraa20103 小时前
python whisper生成字幕
开发语言·python·whisper
努力努力再努力wz3 小时前
【c++进阶系列】:万字详解多态
java·linux·运维·开发语言·c++
秦亿凡3 小时前
多线程下为什么用ConcurrentHashMap而不是HashMap
java·开发语言