算法【Java】 —— 滑动窗口

滑动窗口

在上一篇文章中,我们了解到了双指针算法,在双指针算法中我们知道了前后指针法,这篇文章就要提到前后指针法的一个经典的使用 —— 滑动窗口,在前后指针法中,我们知道一个指针在前,一个指针在后,但是两个指针都是向前移动,如果两个指针之间的元素或者下标之间的差值有一些别的作用或者含义的时候(本质上就是子数组或者子串),我们会使用到滑动窗口这个算法思想,下面以第一道例题为引例。

题目实战

引例 —— 长度最小的子数组

https://leetcode.cn/problems/minimum-size-subarray-sum/

在这里插入图片描述

这道题目要求我们找到最少元素构成的子数组之间所有的数据总和大于等于 target , 也就是找子数组的题目,如果使用暴力美学,那么就需要枚举出所有的子数组,然后进行比较,时间复杂度为 O(N ^ 2)

暴力美学也是使用的是前后指针法,但是由于没有利用好递增关系,所以你也可以认为滑动窗口也是前后指针法的进阶,我们先这样看,使用一个指针遍历数组,然后使用 count 来统计遍历过的元素之和,由于数组的元素是正整数,所以 count 会随着 指针的移动而增加,在数学上,我们认为这是一种递增关系。

这样我们可以先让一个指针遍历数组(这个可以叫进窗口),直到 count >= target 的时候,就需要更新最短的长度,以及让 后指针 向前移动(这个可以叫出窗口),直到 count < target,此时这个循环会让count 减小,这也就让长度减小,所以要在循环中也顺便更新最短的长度。这就是滑动窗口的思路

思考为什么前指针不需要后退,而是直接向前遍历即可?
在暴力美学中,前指针都是回到后指针前一个位置,然后开始继续向前遍历计算新的 count,但是由于具有递增关系,前指针回退是没有必要的,因为已经计算好当前的 count 了,你的回退反而浪费原本的count 这个数值。只需要继续向前遍历即可,我们这个 count 在滑动窗口是动态变化的,根据前指针移动而增加,根据后指针移动而减少。
所以滑动窗口是对前后指针法的暴力美学的优化
在思考问题的时候,我们优先会想到暴力美学,因为它直观也容易想到,如果符合滑动窗口的特性,我们就可以尝试优化。

总结一下滑动窗口的算法思想:
1.进窗口
2.出窗口
3.更新答案(下面是说成数据)(根据题意调整具体位置)

时间复杂度为O(2N) = O(N)

class Solution {public int minSubArrayLen(int target, int[] nums) {int len = nums.length;int count = 0;int ans = Integer.MAX_VALUE;for(int left = 0, right = 0; right < len; right++) {//进窗口count += nums[right];//出窗口while(count >= target) {ans = Math.min(ans, right - left + 1); //更新数据count -= nums[left++];}}return ans == Integer.MAX_VALUE ? 0 : ans;}
}

无重复字符的最长子串

https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/

在这里插入图片描述

解析:
如果使用暴力美学,还是前后指针法,由于这是求最长的字符串(也就是子串问题),对于子串子数组我们优先会想到滑动窗口算法。

在计算不重复的字符串的时候,前指针遇到不重复的字符,两个指针之间的距离就会自增,如果遇到重复的字符,后指针就会向前移动进行去重,按照暴力美学,前指针应该回退到后指针的前一个位置,但是有必要回退吗?首先因为经过后指针的去重,此时前后指针之间就没有重复的字符,这时候没有必要前指针回退,而这时候就是滑动窗口算法的特点。

使用滑动窗口,将不重复的字符加入到窗口中,当遇到重复的字符后指针就向前移动进行去重,再进行完进窗口和出窗口后,我们就可以更新最长长度了。

如何去重呢?大家可以使用Java 给我们提供的 HashSet,也就是哈希表进行去重。

补充:字符串转化成 字符数组的 方法 是 toCharArray().

本题使用 滑动窗口 + 哈希表,时间复杂度为O(2N) = O(N)

class Solution {public int lengthOfLongestSubstring(String ss) {int ans = Integer.MIN_VALUE;char[] s = ss.toCharArray();int len = s.length;Set<Character> set = new HashSet<>();for(int left = 0, right = 0; right < len; right++) {char ch = s[right];if(!set.contains(ch)) {set.add(ch); //进窗口} else {while(ch != s[left++]) {set.remove(s[left-1]);//出窗口}}//更新数据ans = Math.max(ans,right - left + 1);}return ans == Integer.MIN_VALUE ? 0 : ans;}
}

最大连续1的个数

https://leetcode.cn/problems/max-consecutive-ones-iii/description/

在这里插入图片描述

解析:
由于这是字数组问题,可以考虑滑动窗口,由于题目给定的 K 是最大零数字的承载量,所以如果超过这个量就需要进行去重操作,后指针直接前移,在零元素的前一个位置停下即可,这是出窗口,进窗口的条件很简单就是小于等于承载量的时候,更新数据放在最后,这样就保证这是一个符合题意的子数组。

承载量如何判断?引入一个变量 count 来记录当前的零元素个数

class Solution {public int longestOnes(int[] nums, int k) {int ans = Integer.MIN_VALUE;int len = nums.length;int count = 0;for(int left = 0, right = 0; right < len; right++) {//进窗口if(nums[right] == 0) {count++;}//出窗口if(count > k) {while(nums[left++] != 0) {;}count--;}//更新数据ans = Math.max(ans, right - left + 1);}return ans;}
}

将 x 减到 0 的最小操作数

https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/

在这里插入图片描述

解析:
由于每次只能减去最左端或者最右端的数值,所以如果最后有结果的话,那就是数组减去左区间和右边间,这两个区间的所有数字之和恰好等于 x
在这里插入图片描述
题目要求求出最小的操作数,说明左右区间的长度之和要最小。

如果正着求比较难的话,我们可以尝试反着求,也就是求中间部分最长,求中间部分一看就知道可以使用滑动窗口的方法。中间部分的数值应该等于 数组的所有数据之和减去 x ,我这里使用 target 变量来接受。

现在就是处理滑动窗口了,使用 sum 来接受此时 前后指针之间的所有的数据之和,先进窗口(sum 加数据),然后如果 sum > target 的时候就要出窗口(使用循环),最后更新数据的条件就是当 target == sum 时,进行更新数据。

细微条件处理:当数组的所有的数据之和小于 x 的时候,直接返回 -1 ,不能进入滑动窗口的部分,因为 target 计算后 sum - x = target < 0,在滑动窗口中 sum 始终大于 taget ,最终会一直出窗口,然后发生数组越界访问。

class Solution {public int minOperations(int[] nums, int x) {int sum = 0;for(int y : nums) {sum += y;}int target = sum - x;if(target < 0) {return -1;}int len = nums.length;int count = Integer.MIN_VALUE;sum = 0;for(int left = 0, right = 0; right < len; right++) {//进窗口sum += nums[right];//出窗口while(sum > target) {sum -= nums[left++];}//更新数据if(sum == target) {count = Math.max(count, right - left + 1);}}return count == Integer.MIN_VALUE ? -1 : len - count;}
}

水果成篮

https://leetcode.cn/problems/fruit-into-baskets/

在这里插入图片描述

解析:
最多只能装两种水果类型,题目要求我们要求出最大的采摘数量,显而易见的要使用滑动窗口。
进窗口,直接边遍历边进。
出窗口,就是遇到第三个水果类型,需要进行调整。
更新数据,在前两步骤做好就可以进行数据的更新了。

现在就要讨论用什么东西来装水果?
我们可以使用哈希表,Map 来接受水果类型和该水果目前的数量。

整体思路就是 哈希表加滑动窗口

class Solution {public int totalFruit(int[] fruits) {Map<Integer,Integer> map = new HashMap<>();int len = fruits.length;int count = Integer.MIN_VALUE;for(int left = 0, right = 0; right < len; right++) {//进窗口map.put(fruits[right],map.getOrDefault(fruits[right],0) + 1);//出窗口while(map.size() > 2) {map.put(fruits[left],map.get(fruits[left]) - 1);if(map.get(fruits[left]) == 0) {map.remove(fruits[left]);}left++;}//更新数据count = Math.max(count, right - left + 1);}return count;}
}

找到字符串中所有字母异位词

https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/

在这里插入图片描述

解析:
还是子串问题,使用滑动窗口。由于涉及到字符串的查找,所以我们可以使用 toCharArray() 方法将字符串转化为数组,便于我们的使用。

如何存储两个字符串的内容:我们会想到使用哈希表,使用Map 来接受 字母 —— 出现数量 这个键值对,但是题目说了字符串只包含小写字母,那我们可以自己创建两个数组来模拟哈希表,而且使用数组更加方便。

如何比较两个字符串的内容?很多老铁一定想过使用循环遍历,而且我们还是使用数组来模拟哈希表,那就更加方法,这里介绍一种更好的方法,不需要循环遍历就可以比较成功。
使用一个变量来接收有效的字符数量。

如何使用?
我们在进出窗口的过程中顺便统计有效字符的个数,什么是有效的字符,我们需要和另一个哈希表的内容进行判断
这也就说明了我们要事先制作好一个哈希表,然后才开始遍历字符串 s
在遍历过程中,s 的哈希表对应的下标的元素先自增,然后开始判断,当这个字符也存在于 p 的哈希表中,并且 s 对应的元素值要小于等于 p 哈希表的,就算作有效字符
如果是大于 p 的,说明这个字符要么是重复字符要么是 p 哈希表不存在的字符。

出窗口:什么时候要出窗口?答:当当前的字符串长度大于 字符串 p 的长度,因为我们在进窗口的时候,只会统计有效字符个数,当有效字符个数等于 p 字符串的时候,才会更新数据,而此时满足当前 前后指针夹着的字符串全是有效字符 这个条件,只要当前的字符串的长度大于 p 字符串的长度的时候,就一定不是有效字符串,这时候就可以出窗口,顺便调整 s 哈希表的内容,由于此时的调整可能会删除掉有效字符,所以这个也要考虑有效字符的数量的变化,如果你要删除的字符对应的 s 哈希表的数值 小于等于 p 哈希表的时候 —— 有效字符数量就要自减,并且出窗口的次数实际上就是一次,所以用不到循环。

class Solution {public List<Integer> findAnagrams(String ss, String pp) {List<Integer> list = new ArrayList<>();char[] s = ss.toCharArray();char[] p = pp.toCharArray();int[] hash1 = new int[26];int[] hash2 = new int[26];for(char x : p) {hash2[x-'a']++;}int len = s.length;int count = 0;int size = p.length;for(int left = 0, right = 0; right < len; right++) {char ch = s[right];//进窗口if(++hash1[ch-'a'] <= hash2[ch-'a']) {count++;}//出窗口if(right - left + 1 > size) {if(hash1[s[left]-'a']-- <= hash2[s[left]-'a']) {count--;}left++;}//更新数据if(count == size) {list.add(left);}}return list;}
}

串联所有单词的子串

https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/

在这里插入图片描述

解析:
这道题目和上一道单词的异位词很相似,只不过这道题目是以单词为单位的。
不过我们如果将单词视为一个字符的话,思路和上面的一模一样。
使用两个哈希表存放单词。
如何划分字符串,由于 words 中每个单词的长度都是一致的,所以我们可以将字符串按照这个数量进行划分。

我们从暴力美学出发,如果要找出所有的子字符串,那么就需要两层循环。
在暴力美学的内层循环中我们要找到符合的子串,可以使用滑动窗口来求解,因为要比较的单词的长度都是一致的,所以我们可以按照单词的长度对字符串进行划分,也就是说在内层循环中使用滑动窗口的时候,指针每次移动都是加单词的长度

那么外层循环有必要遍历整个字符串吗?
答:没有必要,因为单词的长度是一致的,所以你单词的划分最多的情况就是单词的长度:
在这里插入图片描述
当外层循环遍历次数超过单词长度的时候,你会发现单词的划分和前面的循环重合,所以这些超过单词长度的循环次数是没有必要的,你如果硬要遍历的话,也可以,就是时间复杂度差了些。

所以继续优化的话,最外层循环次数就是单词的长度

最后就是要注意方法的使用了,如何划分字符串,使用 substring(), 哈希表的使用方法要注意 getOrDefault(),如果不存在的话可以返回默认值,但是不会加入到哈希表,其他的就不说了,不了解这些方法的,可以去我往期文章中查阅:String 类
JavaDS —— 二叉搜索树、哈希表、Map 与 Set

class Solution {public List<Integer> findSubstring(String s, String[] words) {List<Integer> list = new ArrayList<>();Map<String,Integer> mapW = new HashMap<>();for(String x : words) {mapW.put(x,mapW.getOrDefault(x,0) + 1);}int lenS = s.length();int len = words[0].length();Map<String,Integer> mapS = new HashMap<>();int count = 0;int size = words.length;for(int i = 0; i < len; i++) {mapS.clear();count = 0;for(int left = i, right = i; right + len <= lenS; right+=len) {String str = s.substring(right,right+len);mapS.put(str,mapS.getOrDefault(str,0) + 1); //进窗口if(mapS.get(str) <= mapW.getOrDefault(str,0)) {count++;} //出窗口if(right - left + 1 > len * size) {String ch = s.substring(left,left+len);if(mapS.get(ch) <= mapW.getOrDefault(ch,0)) {count--;}mapS.put(ch,mapS.get(ch)-1);left += len;}//更新数据if(count == size) {list.add(left);}}}return list;}
}

最小覆盖子串

https://leetcode.cn/problems/minimum-window-substring/

在这里插入图片描述

解析:
还是一个子串问题,使用滑动窗口,由于字符串全是英文字母组成,我们可以使用数组来模拟哈希表的映射关系。
这里我们可以使用 count 变量来统计有效字符个数。
因为题目要求出最小覆盖子串,所以难免会有重复或者不存在于 t 的字符出现,所以为了便于判断是否已经容纳好 t 的所有字符,减少循环出现,我们可以使用额外的变量来统计有效字符个数

进窗口:这个无需多言,直接进

出窗口和更新数据要小心点,当 count 等于有效字符个数的时候,我们要更新数据,那什么时候出窗口呢?如果按照之前的讨论进窗口——出窗口——更新数据是很难实现的,在最开始的时候,我就跟大家提到更新数据这个操作要按照实际情况来讨论,在这道题目就体现出来了。

首先更新完数据后,我们希望在下一个滑动窗口循环中能得到一个残缺的字符串,就是 count 要小于 有效字符总数这一个条件,所以在更新数据之后,我们可以进行出窗口的操作,要注意 count 的 变化,既然要达到 count 要小于 有效字符总数这个条件,我们可以在更新数据的时候写一个循环,正好随着出窗口的过程中一起将数据更新。

class Solution {public String minWindow(String ss, String tt) {String str = "";if(ss.length() < tt.length()) {return str;}int lenS = Integer.MAX_VALUE;int[] hash1 = new int[128];int[] hash2 = new int[128];char[] s = ss.toCharArray();char[] t = tt.toCharArray();for(char x : t) {hash2[x]++;}int len = s.length;int count = 0;int size = t.length;for(int left = 0, right = 0; right < len; right++) {//进窗口char ch = s[right];hash1[ch]++;if(hash1[ch] <= hash2[ch]) {count++;}   while(count == size) {//更新数据if(lenS > right - left + 1) {lenS = right - left + 1;str = ss.substring(left,right+1);}//出窗口char x = s[left++];if(hash1[x]-- <= hash2[x]) {count--;}}}return str;}
}

小结

滑动窗口一般用于子数组或者子串问题上

滑动窗口的基本思路:
1.设置好两个指针 (left = 0, right = 0)
2.进窗口控制 right 指针
3.出窗口控制 left 指针
4.根据实际情况更新数据

一些注意事项:
如果发现字符串完全由字母组成,可以直接使用数组来模拟哈希表,这样做会方便很多
如果涉及到有效字符或者有效数字的时候,可以引入一个变量 来进行统计

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

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

相关文章

JavaScript初级——运算符

一、算数运算符 1、运算符也叫操作符。通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果。 比如&#xff1a;typeof 就是运算符&#xff0c;可以获得一个值的类型&#xff0c;他会将该值的类型以字符串的形式返回 &#xff08;number、string、boolean、undefi…

【秋招笔试】8.12-4399秋招(第一套)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

计算机网络12——IM聊天系统——项目分析和架构搭建

1、IM——聊天系统主要功能 &#xff08;1&#xff09;注册 根据&#xff1a;昵称&#xff0c;手机号&#xff0c;密码 &#xff08;2&#xff09;登录 根据&#xff1a;手机号&#xff0c;密码 &#xff08;3&#xff09;添加好友 根据&#xff1a;昵称 &#xff08;4&…

Secure CRT 9.x版本高亮着色配置文件

Secure CRT的网络配置文件高亮显示&#xff0c;还在完善&#xff0c;逐渐适配不同厂商 设备名字自动蓝色高亮显示设备接口名高亮显示IPv4地址、IPv6地址、MAC地址高亮显示掩码、反掩码高亮显示设备SN号高亮显示接口状态、设备状态等高亮显示各路由协议高亮显示 【下载地址】效果…

XSS-复现dom破坏案例和靶场

目录 xss注入原理&#xff1a; xss是什么&#xff1f; xss原理&#xff1a; DOM&#xff1a; 闯关&#xff1a; 第一关&#xff1a;Ma Spaghet! 源码&#xff1a; 要求&#xff1a; 分析&#xff1a; 第二关&#xff1a; Jefff 源码&#xff1a; 要求&#xff1a; …

ubuntu基于sealos搭建k8s集群,helm3安装配置自动化扩容Prometheus,grafana出图展示,以及动态web搭建

1.项目简介 大方向&#xff1a;k8s云原生方向&#xff0c;运维技术&#xff0c;配置问题解决 解决技术:ubuntu模板机安装&#xff0c;配置远程xshell连接ubuntu&#xff0c;设置静态ip&#xff0c;换ubuntu阿里云源&#xff0c;配置集群间域名解析&#xff0c;解决双IP冲突网…

ubuntu下使用docker、socket、yolov5进行图像处理数据交互记录

ubuntu下使用docker、socket、yolov5进行图像处理数据交互记录 概述&#xff1a;主要实现了在宿主机上通过8000端口传递一张图像给docker镜像&#xff0c;然后镜像中处理后&#xff0c;通过8001端口回传处理后的图像给宿主机。 第一章、构建镜像 一、dockerfile文件 1.拉取…

尚品汇-前端面包屑平台属性、排序处理(三十三)

目录&#xff1a; &#xff08;1&#xff09;面包屑处理平台属性 &#xff08;2&#xff09;排序处理 &#xff08;2&#xff09;单点登录业务介绍 &#xff08;1&#xff09;面包屑处理平台属性 前端显示&#xff1a;面包屑显示效果 搜list搜索方法继续添加返回的平台属性…

Lora 全文翻译

作者&#xff1a; 地点&#xff1a;hby 来源&#xff1a;https://arxiv.org/pdf/2106.09685 工具&#xff1a;文心 LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS 摘要 自然语言处理的一个重要范式包括在通用领域数据上进行大规模预训练&#xff0c;并适应特定任务或…

php 在app中唤起微信app进行支付,并处理回调通知

<?phpnamespace app\api\controller;use think\facade\Db; use think\facade\Log;class Wxzf {

什么是视频比特率?与视频时长是什么关系

​ ‌比特率是指单位时间内传输或处理的比特的数量&#xff0c;单位为‌bps(‌bit per second)。‌ 比特率经常用于描述在电信和计算领域中数据传输的速度&#xff0c;也可以作为衡量音频和视频文件数据率的指标。比特率越高&#xff0c;传送的数据越大&#xff0c;音频或视频…

Notion快速使用

探索Notion&#xff1a;全能笔记软件的新世界 1. 什么是Notion&#xff1f; 在数字化时代&#xff0c;一款集颜值与功能于一身的笔记软件无疑是学习与工作的得力助手。今天&#xff0c;我要向大家介绍的就是这样一款全能型选手——Notion。Notion不仅以其高颜值在众多笔记软件…

拟南芥中基因家族序列的提取

1.拟南芥基因组数据的下载 phytozome 是一个收录植物基因组数据的网站&#xff0c;数据整理比较规范&#xff0c;已 经提供了去除可变剪切的 cds 和 protein 序列文件。只有 gff3 文件需要 过滤处理 2. 对拟南芥的注释文件gff3文件进行ID处理&#xff0c;最终得到以下4个文件 …

【uni-app】使用天气API做一个天气APP(全过程)- 实况、逐小时、40日

头一次使用uni-app写代码, 现学现卖, 写的不好的地方见谅, 申请个appid就可以运行 切换城市界面比较简单, 城市名称需要符合天气api参数规则, 录入的城市不要带市区县; 格式如: 青岛、铁西、海淀、沛县 APP效果 功能说明 实况天气逐小时预报未来7日天气未来40日天气空气质量详…

C语言 | Leetcode C语言题解之第336题回文对

题目&#xff1a; 题解&#xff1a; #define SIZE 9470 #define N 168000 #define P 13331typedef unsigned long long ULL; ULL p[301];//p[i]存储P^ivoid init()//初始化p进制次幂数组 {int i;p[0]1;for(i1;i<300;i){p[i]p[i-1]*P;} }int** palindromePairs(char**words,…

探索 Resolume Arena 7 - 引领 VJ 音视频创作的卓越软件

Resolume Arena 7 是一款专为 Mac 和 Windows 系统设计的强大 VJ 音视频软件&#xff0c;为创意专业人士和爱好者提供了丰富而出色的功能。 这款软件拥有直观且用户友好的界面&#xff0c;即使对于初学者来说&#xff0c;也能快速上手并开始创作。其强大的媒体管理功能&#x…

SpringBoot事务-调度-缓存

一.Spring Boot中的事务管理 设置事务 Transactional(isolation Isolation.DEFAULT) Transactional(propagation Propagation.REQUIRED) 开启事务 EnableTransactionManagement 1. 开启事务管理 要开启 Spring 的事务管理&#xff0c;你需要在你的 Spring Boot 应用中添加 …

大数据技术现场工程师特色实训室解决方案

一、引言 在大数据时代背景下&#xff0c;数据已成为新的生产要素&#xff0c;驱动着各行各业的创新发展。面对这一趋势&#xff0c;市场对于既掌握大数据理论知识又具备实战能力的大数据技术人才的需求急剧增加。为了应对这一挑战&#xff0c;唯众精心设计了一套全面的大数据…

详解golang内存管理

介绍 要搞明白 Go 语言的内存管理,就必须先理解操作系统以及机器硬件是如何管理内存的。因为 Go 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…

FPGA资源评估

FPGA资源评估 文章目录 FPGA资源评估前言一、资源评估1.1 资源有哪些1.2 资源统计 二、 FPGA 的基本结构三、 更为复杂的 FPGA 架构 前言 一、资源评估 大家在项目中一般会要遇到需要资源评估的情况&#xff0c;例如立了新项目&#xff0c;前期需要确定使用什么FPGA片子&…