算法篇-----滑动窗口

1.概念

所谓的滑动窗口,就是我们之前的双指针的一个扩展应用,在上一章中,我们的双指针是相向而行的,而这里的双指针是同向而行的,由于其移动过程中像一个窗口一样来回滑动,时大时小,而且还会来回动,因此我们给他起了一个名字:滑动窗口

2.使用场景

求给定数组的子数组时可能会用到,具体可以结合下文的示例来感受

3.例题

例1:长度最小的子数组

209. 长度最小的子数组 - 力扣(LeetCode)

这个题要求我们求找出该数组中满足其总和大于等于 target 的长度最小的 子数组,符合滑动窗口的使用场景,下面我将具体阐述解题方法。

解题思路:

由于数组里面全是正数,并且是求和,因此我们可以利用一下单调性(肯定不会出现越加越小的情况),初始状态下我们设置两个指针left和right,并都指向下表为0的位置,并且假设left是左边框,right是右边框,设计一个求和的变量,right每次走的时候都自动计算一下当前的总和,并与target比较,如果小于target就继续++,如果大于等于target就先计算一下长度,并且长度要和之前的长度值进行比较,取最小的,由于我们要去最小的length,所以我们可以让left++,再求一下和(这里可以直接让sum-=arr[left++])就好,之后不断重复上述过程,直到找到最短的length位置。

参考代码:

class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int n=nums.size();int left=0,right=0;int sum=0;int len=INT_MAX;for(int left=0,right=0;right<n;right++){sum+=nums[right];    //进窗口while(sum>=target)   //判断{len=min(len,right-left+1);    //更新结果sum-=nums[left++];     //出窗口}}return len==INT_MAX?0:len;}
};

例2:无重复字符的最长字串 

3. 无重复字符的最长子串 - 力扣(LeetCode)

这道题要求我们寻找一个没有重复字符的字串,而且字串是要连续的,那么我们还是两种方法来解决这道题

解题思路:

方法一)暴力破解

将每个符合的子字符串都写出来,分别求长度

方法二)滑动窗口

我们假设有这样一个数组[ d e a b c a b c],先设置两个指针,left和right,之后让right往后走(进窗口),与此同时,我们可以借用一下哈希表,由于哈希的本质就是映射,而题里面都是常见字符,为此我们可以用数组模拟哈希表,定义个hash[128],哪个字符进去了,就在其映射的哈希表的位置上做上标记,完成这一步后,让right一直往后走,直到right所对应的字符对应的映射在之前的哈希表中出现过为止,如上例中,left=0,right走到第二个a处停止,此后,由于我们的right已经给我们“趟过雷”了,此时将left在++到e的位置,right在从e开始走就多此一举了,可以让left直接++到第一个a后面的字符,也就是b,然后right继续往前走就好,不断重复上述过程,直到我们找到最优解为止。

参考代码:

class Solution {
public:int lengthOfLongestSubstring(string s) {int hash[128]={0};int left=0,right=0,n=s.size();int ret=0;while(right<n){hash[s[right]]++;    //将right位置的数映射到哈希表上,是我们的入窗口的操作while(hash[s[right]]>1){//这个位置之前被映射过了,证明当前的是重复的字符//出窗口,找到没有映射的位置hash[s[left]]--;    //我们的left准备向右移动了,对应的映射也需要变化了left++;             //上例中,当left++到第一个b的时候,原来第一个a的映射就没了,我们也就跳出循环了}ret=max(ret,right-left+1);        //更新结果right++;                         //让下一个元素进入窗口}return ret;}
};

例3:翻转数字(最大连续1)

1004. 最大连续1的个数 III - 力扣(LeetCode)

这道题题意很简单,要求我们可以最多反转K个0,之后找出最长的1的子字符串

解题思路:

这道题有一些小坑,如果直接考虑反转,那可能有点麻烦了,我们不妨转换一下题意,也就是说找一个字符串,其0的个数不超过K个就可以。这道题还是有两种解题思路

法一)暴力枚举+计数器

分别举出每一段子字符串,并且0的个数要符合要求,之后找出最长的子字符串

法二)滑动窗口+计数器

我们可以仿照上一个题,定义两个指针Left和right,以及一个计数器count,让right不断往右走,遇到零计数器就++,否则不触发任何反应,继续往右走(进窗口),当计数器达到k的时候(判断条件),让left也往左走(出窗口),1则不触发任何反应,继续往右走,0则计数器--,重复上述操作,直至right走到头并且符合判断条件

参考代码:

class Solution {
public:int longestOnes(vector<int>& nums, int k) {int ret=0,left=0,count=0,right=0;for(right=0;right<nums.size();right++){//进窗口if(nums[right]==0){count++;}while(count>k){if(nums[left]==0){count--;}left++;}ret=max(ret,right-left+1);}return ret;}
};

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

1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)

这道题给定我们一个数,并要求我们在数组内依次选一个数来减去x,直到x为0,如果存在则返回最小操作数,如果不存在则返回-1,这道题看起来貌似有一些困难,那我们应该怎么做呢?

解题思路:

想必大家都学过高数,在数学中,我们了解到正难则反,此题同理,我们只需要找到中间最长的子数组,并且其和target等于该数组元素总和减去x即可,这样,我们便将题迎刃而解了

我们不妨设两个指针left和right,以及和total,之后开始进窗口操作,即将Total增加,之后再判断total与target的关系即可,如果大于,那么便出窗口,如果等于就更新结果,最后找最长的子数组就Ok,最后返回最小操作数

代码示例:

class Solution {
public:int minOperations(vector<int>& nums, int x) {int sum=0;for(int a:nums){sum+=a;}int target=sum-x;if(target<0){return -1;}int ret=-1;for(int left=0,right=0,total=0;right<nums.size();right++){total+=nums[right];while(total>target){total-=nums[left++];}if(total==target){ret=max(ret,right-left+1);}}if(ret==-1){return ret;}else{return nums.size()-ret;}}
};

 例4:水果成篮

904. 水果成篮 - 力扣(LeetCode)

这道题像是一个小的阅读理解一样,我们可以简单分析一下,并借助注释简化一下题中的表达思想,最终,我们可能将题目简化为在一个数组中找长度最长的子数组,并且里面只能由两种数(水果种类),由此我们便可以开始解决这道题了

解题思路:

由于所有的解题思路都是由暴力解法演变而来的,所以我们在这里先讲解一下暴力解法

方法一)暴力破解+哈希表

这种方法思路很简单,就是从头开始,挨个列举出来子数组的长度,找到最大值后返回,那如何判断此时的子数组达到最大值呢?我们这里可以借助哈希表,利用映射关系,当哈希表的长度等于2时,便达到了最大长度。如下图所示

方法二)滑动窗口+哈希表

这种方法是基于暴力解法转换而来的,当水果种类Kinds==2时,我们必然要移动左指针,这时就会出现两种情况,一种是kinds减小,另一种是kinds不变,原因很简单不解释了,对于第一种情况,右指针不能动,对于第二种情况,右指针要右移,为此我们找到了这道题的破解点,滑动窗口。具体解法就是设置两个指针,还是进窗口出窗口判断等操作,对于进窗口操作,直接借助哈希表的映射,让hash[f[right]]++,出窗口就是hash[f[left]]--,但是有一点值得注意的是我们还要记录一下每种水果已经有了多少个,,并且最好还要有一个计数的,所以我们用hash<int,int>,也可以借助unordered_map来实现,有一个小细节就是我们希望减到0时就自动把这个水果给删掉,以免影响水果种类的判断,判断条件就不多说了,就是hash.size()>2,我们就出窗口

代码示例:

class Solution {
public:int totalFruit(vector<int>& fruits) {unordered_map<int,int> hash;int ret=0;for(int left=0,right=0;right<fruits.size();right++){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);}return ret;}
};

这样或许时间复杂度有点高,我们给改一下,由于题目给定了我们水果的个数,所以我们可以直接利用数组来模拟哈希表,只不过要再增加一个变量Kinds,相当于用空间换时间了。

改造后代码:

class Solution {
public:int totalFruit(vector<int>& fruits) {int hash[100001]={0};int ret=0;for(int left=0,right=0,kinds=0;right<fruits.size();right++){//这里维护的是水果种类,不是个数,不能写成进窗口就单纯的kinds++,要看看之前有没有这种 水果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);}return ret;}
};

改造完成!

例5:找字母异位词

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

这道题要求我们在一个字符串中,找一个子字符串,要满足字母组成上可以与给定字符串不同,但必须是由这几个字符所构成,并返回第一个字符的索引。

解题思路:

题目既然要求我们找与给定字符串异位的子字符串,那么我的第一想法是利用哈希表,统计出给定字符串中每个字符出现的次数。之后我们在利用哈希表,并通过滑动窗口的方法,统计窗口里面的每一个字符出现的个数,这里为什么会联想到滑动窗口呢?原因就是因为我们这两个指针之间的距离是固定的,就是给定字符串的长度,right++必然就要left++,更像滑动窗口了,好我们回到问题本身,我们在统计窗口里面的每一个字符出现的个数之后,可以再设置一个计数器count,目的是记录我们有效的字符,举个例子,比如给定字符串由3个字符a,而我现在窗口里面又滑进来一个a,现在假设我们的总共在窗口里面又3个,那新进来的a就是有效字符,而换句话讲,如果我现在窗口里面已经6个a了,那新来的这个a就没啥用了,count就不需要++,如此一来,只需要我们的count与给定字符串的长度相等,那就证明符合异位,这就是我们需要的子字符串,出窗口同理,也要维护一下count

代码实现:

class Solution {
public:vector<int> findAnagrams(string s, string p) {vector<int> ret;int hash1[26]={0};      //统计字符串P中每个字符出现的个数for(auto e:p){hash1[e-'a']++;}int hash2[26]={0};      //统计窗口里面每个字符出现的个数int m=p.size();for(int left=0,right=0,count=0;right<s.size();right++){char in=s[right];hash2[in-'a']++;if(hash2[in-'a']<=hash1[in-'a']) count++;    //进窗口+维护countif(right-left+1>m)//判断     要保证子字符串的长度{char out=s[left++];if(hash2[out-'a']--<=hash1[out-'a']) count--;  //出窗口+维护count}//更新结果if(count==m) ret.push_back(left);}return ret;}
};

例6:串联所有单词的子串

30. 串联所有单词的子串 - 力扣(LeetCode)

这道题是我们上一道题的延申,我们可以将题目的意思翻译一下,就是说在大字符串里面找小字符串,并且小字符串与word字符串是可以异位的

解题思路:

由于给定字符串中每个单词的长度是一样的,所以在这里我们可以将长字符串分割成几个短字符串。在上述事例中,w有两个三个字母的单词组成。那么我们便可以将大字符串三个字符为一组分割成若干份,从头开始画,也许有人会问,万一是从第二个字符开始它才是符合的,那应该怎么办?这里边要借助我们的滑动窗口来解决了。我们可以让起始滑动的位置从左向右依次移动,当移动到第二个起始位置之时,及上述事例的字母f,此时我们便可以停止移动,因为再移动也是重复的没有意义,那我们怎么确定f的位置呢?这里边又要借用w中每个单词的长度是相等的,可以求出单词的长度,这样就可以确定出f的位置。之后再利用哈希映射等方法可以破解此题,破解方法与上一题相似。

参考代码:

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> ret;unordered_map<string,int> hash1;     //保存words里面所有单词的频次for(auto& e:words)  hash1[e]++;int len=words[0].size();        //求出一个单词有几个字符int m=words.size();      //求出有几个单词for(int i=0;i<len;i++)       //执行Len 次{unordered_map<string,int> hash2;    //维护窗口内单词的频次for(int left=i,right=i,count=0;right+len<=s.size();right+=len){//进窗口+维护 countstring in=s.substr(right,len);hash2[in]++;if(hash1.count(in)&&hash2[in]<=hash1[in])    //有效字符串count++;//判断if(right-left+1>len*m){//出窗口+维护countstring out=s.substr(left,len);if(hash1.count(out)&&hash2[out]<=hash1[out])   //有效字符串count--;hash2[out]--;left+=len;}//更新结果// 如果当前窗口包含所有单词,记录起始索引if(count==m)ret.push_back(left);}}return ret;}
};

例7:最小覆盖字串

76. 最小覆盖子串 - 力扣(LeetCode)

这个题要求我们在一个字符串中找的一个最小字串,并且这个最小字串要包含给定的另一个字串,那么我们应该怎么做呢?

解题思路:

法一)暴力破解+哈希表

这道题基本思想和前面的差不多,我们第一种想法就是暴力破解,我们可以先遍历字符串2,探明要寻找的字母类型和个数,并且可以将其映射在哈希表中,之后我们再一一例举字符串1的所有符合题目条件的子字符串,并找出最小子字符串,完成本题

法二)滑动窗口+哈希表

我们还是遍历字符串2,并将每个字符都入hash2

之后可以定义两个指针left和right,之后让right往后走,每次走之前都让right所指元素入哈希表,当right入的是有效字符(即是hash2里面的字符)时,可以check(hash2,hash1),符合条件就更新结果,之后让left++,可能还会有两种结果,一种是符合要求,那我们的right就不用动,另一种是不符合要求,那我们的right就要++了,不断重复上述操作,直至循环结束!

优化:

由于我们的每一次check的花销都会很大,因为需要遍历一下hash1和hash2,而且指针只要碰到一个有效字符就要check一下,那倘如字符串2里面有一万个a,我们的字符串里面有两万个a,那我们的编译器岂不是不用干别的了?因此,可以将上述代码给出优化!

优化的方法也很简单,我们可以定义一个变量count,用于标记有效字符的种类,并且在进窗口和出窗口时对其进行维护,具体来说就是在进窗口之后,当hash2[in]==hash[1]时count++,那为什么是=而不是>呢?原因就是=的时候就已经符合条件了,如果>的时候再去统计就会导致这个字符重复统计,并且也没有必要,然后在出窗口之前,如果将要出去的字符符合hash2[out]==hahs1[out],那么我们的count就要--,之后我们的判断条件就可以变为count==hash1.size()

参考代码:

class Solution {
public:string minWindow(string s, string t) {int hash1[128]={0};int kinds=0;     //统计有效字符有多少种int hash2[128]={0};for(auto e:t){if(hash1[e]==0){kinds++;}hash1[e]++;}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++;}while(count==kinds){if(right-left+1<minlen){minlen=right-left+1;begin=left;}char out=s[left];left++;if(hash2[out]==hash1[out]){count--;}hash2[out]--;}}if(begin==-1){return "";}else{return s.substr(begin,minlen);}}
};

至此,滑动窗口篇章结束!!!

接下来的文章,将会为大家讲解二分查找的算法! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/78773.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.1探索 LLaMA-Factory:大模型微调的一站式解决方案

探索 LLaMA-Factory&#xff1a;大模型微调的一站式解决方案 引言 在大模型的时代&#xff0c;微调技术是将预训练模型适配到特定任务的关键。LLaMA-Factory 作为一款强大的工具&#xff0c;为开发者提供了便捷且高效的大模型微调解决方案。本文将深入介绍 LLaMA-Factory 的基…

神经网络笔记 - 感知机

一 感知机是什么 感知机&#xff08;Perceptron&#xff09;是一种接收输入信号并输出结果的算法。 它根据输入与权重的加权和是否超过某个阈值&#xff08;threshold&#xff09;&#xff0c;来判断输出0还是1。 二.计算方式 感知机的基本公式如下&#xff1a; X1, X2 : …

Pygame事件处理详解:键盘、鼠标与自定义事件

Pygame事件处理详解:键盘、鼠标与自定义事件 在游戏开发中,玩家的交互是至关重要的。无论是移动角色、触发动作还是暂停游戏,都需要通过各种输入来实现。Pygame作为一个功能强大的Python库,提供了丰富的API来处理这些输入,包括键盘、鼠标以及自定义事件。本文将详细介绍如…

使用 Python 项目管理工具 uv 快速创建 MCP 服务(Cherry Studio、Trae 添加 MCP 服务)

文章目录 下载Traeuv 工具教程参考我的这篇文章创建 uv 项目main.pyCherry Studio 添加 MCP 服务申请 DeepSeek API配置 DeepSeek API调用 MCP 服务 Trae 添加 MCP 服务添加 MCP创建智能体 使用智能体调用 MCP 创建 demo 表查询 demo 表结构信息demo 表插入 2 条测试数据查询 d…

为什么要学习《金刚经》

《金刚经》作为佛教般若经典的核心&#xff0c;以"缘起性空"为思想根基&#xff0c;通过佛陀与须菩提的对话&#xff0c;揭示了破除执著、见真实相的智慧。 以下从核心要义、精髓段落和现实应用三个维度进行解读&#xff1a; 一、核心思想精髓 1. "凡所有相&am…

【MQ篇】RabbitMQ之消费失败重试!

目录 引言&#xff1a;消息不丢是底线&#xff0c;失败了优雅重试是修养&#xff01;消费失败了&#xff0c;为啥不能老是原地复活&#xff1f;&#x1f914;智能重试策略一&#xff1a;本地重试&#xff08;Spring Retry 的魔法&#xff09;&#x1f3e0;✨智能重试策略二&…

制作一款打飞机游戏33:碰撞体编辑

我们设置系统的方式使得编辑碰撞检测框&#xff08;即碰撞盒&#xff09;并不容易。所以&#xff0c;我们的下一步是扩展我们的编辑器&#xff0c;尤其是精灵编辑器&#xff0c;以便我们能够在编辑器中直接编辑碰撞盒。 编辑碰撞盒 让我们加载Sprite编辑器。例如&#xff0c;这…

Kotlin和JavaScript的对比

Kotlin和JavaScript有一些相似之处&#xff0c;但也存在显著的差异&#xff0c;下面从多个方面为你详细分析&#xff1a; 相似点 1. 语法灵活性 变量声明&#xff1a;二者在变量声明上都较为灵活。在JavaScript里&#xff0c;借助var、let和const可以声明变量。其中&#xf…

生活需要一些思考

总分总 写文章、做事情、写邮件、写信&#xff0c;都是要【总分总】。 先总【因为没人有耐心一上来就看细节&#xff0c;先总结&#xff0c;别人感兴趣才会看分】 然后分【分中包括多个子部分&#xff0c;或子章节、子目标&#xff0c;他们之间层层递进&#xff0c;最终引出最…

JAVA设计模式——(九)工厂模式

JAVA设计模式——&#xff08;九&#xff09;工厂模式 介绍理解实现ProductFactory测试泛型扩展 应用 介绍 定义一个工厂类的接口&#xff0c;帮助一个实际对象 创建实例&#xff0c;并让其工厂类的子类决定实例化哪个类。 理解 工厂模式中&#xff0c;必定分为了两部分&…

Java后端接口调用拦截处理:注解与拦截器的实现

在Java开发中&#xff0c;对后端接口调用进行拦截处理是一种常见的需求&#xff0c;通常用于权限验证、Token校验、状态更新等操作。本文将围绕 Spring框架的拦截器&#xff08;Interceptor&#xff09;、Spring AOP&#xff08;面向切面编程&#xff09; 和 Spring Security 三…

第14讲:科研图表的导出与排版艺术——高质量 PDF、TIFF 输出与投稿规范全攻略!

目录 📘 前言:导出,不只是“保存”! 🎯 一、你需要掌握的导出目标 🖼️ 二、TIFF / PNG 导出规范(适用于投稿) 🧲 三、PDF 矢量图导出(排版首选) 🧩 四、强烈推荐组合:showtext + Cairo 🧷 五、多个图的组合导出技巧 🧪 六、特殊投稿需求处理 �…

对 FormCalc 语言支持较好的 PDF 编辑软件综述

FormCalc是一种专为PDF表单计算设计的脚本语言&#xff0c;主要应用于Adobe生态及SAP相关工具。以下是对FormCalc支持较好的主流软件及其特点&#xff1a; 1. Adobe LiveCycle Designer 作为FormCalc的原生开发环境&#xff0c;LiveCycle Designer提供最佳支持&#xff1a; …

第二阶段:基础加强阶段总体介绍

Java语法的学习笔记 下面放复习的文档链接&#xff0c;如果有需要可以前往下载获取&#xff0c;这个仓库还有关于mysql、hadoop、python等的复习部分&#xff0c;并且每个文档有着对应的代码部分。文章作为复习使用&#xff0c;更多代码内容见链接如下: https://gitee.com/zha…

大前端开发——前端知识渐变分层讲解 利用金字塔原理简化前端知识体系

Web开发基础 核心概念 HTML、CSS和JavaScript&#xff1a;Web开发的三大基石&#xff0c;分别负责结构、样式和行为。 代码管理&#xff1a;随着项目规模扩大&#xff0c;需要将代码拆分成小块&#xff0c;便于维护。 作用域污染&#xff1a;早期所有代码共享全局作用域&…

Mixture-of-Experts(MoE)原理与在DeepSeek中的应用

MoE机制简介 Mixture-of-Experts(MoE,混合专家)是一种“分而治之”的神经网络架构思想。在MoE模型中,存在多个并行的子网络,被称为“专家”。每个专家通常擅长处理特定类型的输入特征或知识片段。而在模型前向计算时,并非激活所有专家参与运算,而是通过一个专门的门控网…

SpringCloud学习笔记

个人学习进度&#xff1a;视频跟敲笔记&#xff08;12天&#xff09; 学习视频&#xff1a;尚硅谷微服务速通&#xff08;7小时左右课程&#xff09; 资源&#xff1a; 1.pdf&#xff1a;微服务pdf&#xff08;课程&#xff09;&#xff1a;https://pan.baidu.com/s/1g_TAuBjQ…

【大模型】Coze AI 智能体工作流从配置到使用实战详解

目录 一、前言 二、工作流介绍 2.1 什么是工作流 2.2 工作流与对话流 2.2.1 两者区别 2.3 工作流节点介绍 2.3.1 工作流节点说明 2.3.2 开始节点与结束节点 2.4 工作流入口 2.4.1 自定义智能体入口 2.4.2 从资源库新增工作流 2.5 工作流使用限制 三、工作流配置与使…

Discord多账号注册登录:如何同时管理多个账户?

Discord是许多人、特别是游戏玩家和社区管理者的重要沟通工具。随着用户需求的增长&#xff0c;越来越多的人开始在Discord上注册多个账号进行管理。例如&#xff0c;个人和工作账号的区分&#xff0c;多个游戏社区的参与&#xff0c;或者通过不同的身份进行更灵活的社交互动。…

前端如何使用Mock模拟数据实现前后端并行开发,提升项目整体效率

1. 安装 Mock.js npm install mockjs --save-dev # 或使用 CDN <script src"https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>2. 创建 Mock 数据文件 在项目中新建 mock 目录&#xff0c;创建 mock.js 文件&#xff1a; // m…