滑动窗口算法精讲(Sliding Window Algorithm)
文章目录
- 滑动窗口算法精讲(Sliding Window Algorithm)
- 简介
- 步骤及算法模板
- 模板1
- 模板2
- leetcode例题讲解
- 入门级
- 209. 长度最小的子数组
- 思路:
- 代码实现
- 219. 存在重复元素 II
- 思路
- 代码实现
- 220. 存在重复元素 III
- 思路
- 代码实现
- 进阶级
- 395. 至少有 K 个重复字符的最长子串
- 思路
- 代码实现
- 经典题目
- 438. 找到字符串中所有字母异位词 (一题三解)
- 思路1:
- 代码实现1
- 思路2
- 代码实现2
- 思路3
- 代码实现3
- 其它练习题目
滑动窗口算法精讲(Sliding Window Algorithm)
简介
滑动窗口算法的本质是双指针法中的左右指针法,所谓滑动窗口,就像描述的那样,可以理解成是一个会滑动的窗口,每次记录下窗口的状态,再找出符合条件的适合的窗口。它可以将双层嵌套的循环问题,转换为单层遍历的循环问题。使用两个指针一左一右构成一个窗口,就可以将二维循环的问题转化成一维循环一次遍历,相当于通过旧有的计算结果对搜索空间进行剪枝,使时间复杂度从O(n²)降低至O(n),比如经典字符串查找算法Rabin-Karp 指纹字符串查找算法,它本质上也使用了滑动窗口的思想,通过公式推导降低窗口滑动时计算子串哈希值的复杂度。
滑动窗口算法更多的是一种思想或技巧,按照窗口大小是否固定分为固定滑动窗口和变长滑动窗口,可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。往往类似于“请找到满足xx的最x的区间(子串、子数组等)的xx”这类问题都可以使用该方法进行解决。
步骤及算法模板
以下思路是比较形象的滑动窗口问题的解题步骤,但有些题目找到窗口限定比较隐晦,需要具体问题具体分析:
(1)初始化窗口:
初始化左右边界 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
(2)寻找可行解:
我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的满足可行解。
(3)优化可行解:
此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的可行解不再符合要求。同时,每次增加 left,我们都要更新一轮结果。
(4)滑动窗口,直至一次遍历结束:
重复第 2 和第 3 步,直到 right 到达到的尽头。
这个思路其实也不难理解,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
模板1
public void slideWindowTemplate(String nums){int l = 0, r = 0; //[初始化窗口]//codes... [其他初始化信息,定义一些维护数据的数据结构]while(r < nums.length){//codes..... [维护窗口中的数据] while(l < r && check(xxx) == false){ //[窗口不满足某种性质]//codes... [维护窗口中的数据] l++; //[缩小窗口]}//codes.. [更新结果]r++; //[增大窗口]}
}
模板2
public void slideWindowTemplate(String nums){//codes... [其他初始化信息,定义一些维护数据的数据结构]for(int l = 0, r = 0; r < nums.length; r++){//codes..... [维护窗口中的数据] while(l < r && check(xxx) == false){ //[窗口不满足某种性质]//codes... [维护窗口中的数据] l++; //[缩小窗口]}//codes.. [更新结果]}
}
模板1和模板2算法本质是一样的,只是实现不同,读者可根据爱好来选择。
leetcode例题讲解
例题分为三种级别,一是入门级题目,二是进阶题目,三是经典题目分别挑选一道题进行讲解。
入门级
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组[numsl,numsl+1,...,numsr−1,numsr][nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] [numsl,numsl+1,...,numsr−1,numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:输入:target = 4, nums = [1,4,4]
输出:1
示例 3:输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
地址:209. 长度最小的子数组
思路:
本题为变长的滑动窗口,其窗口大小由target决定(窗口总和大于等于target),所以定义一个变量(窗口内总和),小于target右指针右移,大于target左指针右移,等于记录更新窗口大小即可
代码实现
package com.algorithm.num209;/*** 思路:滑动窗口实现,定义一个变量(窗口内总和),* 小于target右指针右移,大于target左指针右移,等于记录更新窗口大小** @author hh* @date 2022-2-3 20:10*/
public class Solution {public int minSubArrayLen(int target, int[] nums) {int result = Integer.MAX_VALUE;int numsLen = nums.length;int winNums = 0;//双指针l,rfor(int l = 0,r = 0; r < numsLen; r++){winNums += nums[r];//如果区间和大于target左指针右移while (winNums >= target){//先更新长度result = Math.min(result,r - l + 1);//缩小左窗口winNums -= nums[l];l++;}}return result == Integer.MAX_VALUE ? 0 : result;}
}
219. 存在重复元素 II
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [1,2,3,1], k = 3
输出:true
示例 2:输入:nums = [1,0,1,1], k = 1
输出:true
示例 3:输入:nums = [1,2,3,1,2,3], k = 2
输出:false
地址:219. 存在重复元素 II
思路
滑动窗口,窗口最大为k,其中r - l <= k,把窗口中的元素保存到集合中,每次左右指针移动更新集合即可,如果集合中存在元素则返回true
代码实现
package com.algorithm.num219;import java.util.LinkedHashSet;
import java.util.Set;/*** 思路:滑动窗口,窗口最大为k,其中r - l <= k,把窗口中的元素保存到集合中,每次* 左右指针移动更新集合即可,如果集合中存在元素则返回true** @author hh* @date 2022-2-3 21:18*/
public class Solution {public boolean containsNearbyDuplicate(int[] nums, int k) {int numLength = nums.length;Set<Integer> set = new LinkedHashSet<>();for(int l = 0, r = 0; r < numLength; r++){//判断集合中是否存在,存在则直接返回true,没有则添加到集合中if(set.contains(nums[r])){return true;}set.add(nums[r]);//如果窗口大小则缩小窗口while (r - l >= k){set.remove(nums[l]);l++;}}return false;}
}
220. 存在重复元素 III
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
示例 2:输入:nums = [1,0,1,1], k = 1, t = 2
输出:true
示例 3:输入:nums = [1,5,9,1,5,9], k = 2, t = 3
输出:false
地址: 220. 存在重复元素 III
思路
本题与219题目非常类型,只不过条件有了变化。所以可以选取数据结构TreeSet来实现。使用TreeSet保存窗口中元素,窗口扩大时需要判断有没有大于等于nums[r] - t的元素,如果有,则返回为true,当达到窗口大小缩小窗口,并移除窗口中的元素即可
代码实现
package com.algorithm.num220;import java.util.TreeSet;/*** 思路:滑动窗口,使用TreeSet保存窗口中元素,窗口扩大时需要判断有没有大于等于nums[r] - t的元素,* 如果有,则返回为true,当达到窗口大小缩小窗口,并移除窗口中的元素即可** @author hh* @date 2022-2-3 21:44*/
public class Solution {public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {int numsLength = nums.length;TreeSet<Long> treeSet = new TreeSet<>();for(int l = 0,r = 0; r < numsLength; r++){long down = (long) nums[r] - t;long up = (long)nums[r] + t;//同时满足上下限Long cellingVal = treeSet.ceiling(down);Long floorVal = treeSet.floor(up);if((cellingVal != null && cellingVal <= nums[r]) || (floorVal != null && floorVal >= nums[r])){return true;}treeSet.add((long)nums[r]);//是否到达窗口的最大while (r - l >= k){treeSet.remove((long)nums[l]);l++;}}return false;}
}
进阶级
395. 至少有 K 个重复字符的最长子串
给你一个字符串 s
和一个整数 k
,请你找出 s
中的最长子串, 要求该子串中的每一字符出现次数都不少于 k
。返回这一子串的长度。
示例 1:
输入:s = “aaabb”, k = 3
输出:3
解释:最长子串为 “aaa” ,其中 ‘a’ 重复了 3 次。
示例 2:输入:s = “ababbc”, k = 2
输出:5
解释:最长子串为 “ababb” ,其中 ‘a’ 重复了 2 次, ‘b’ 重复了 3 次。
地址:395. 至少有 K 个重复字符的最长子串
思路
题目说明了只包含小写字母(26 个,为有限数据),我们可以枚举最大长度所包含的字符类型数量,答案必然是 [1, 26],即最少包含 1 个字母,最多包含 26 个字母。
你会发现,当确定了长度所包含的字符种类数量时,区间重新具有了二段性质。
当我们使用双指针的时候:
右端点往右移动必然会导致字符类型数量增加(或不变)
左端点往右移动必然会导致字符类型数量减少(或不变)
当然,我们还需要记录有多少字符符合要求(出现次数不少于 k),当区间内所有字符都符合时更新答案。
代码实现
package com.algorithm.num395;/*** 思路:滑动窗口,但本题关键是无法确定窗口的大小,由于字母仅限小写英文字母,所以我们可以枚举窗口中字母类型数量,* 从1到26,这样就限定了窗口的大小,从而解决问题。定义一个变量winCharTypeNums保存当前窗口的字母类型数量,当枚举* 字母类型数量大于winCharTypeNums时扩大窗口,等于时更新最大长度,小于时缩小窗口。保存字母数量使用数组保存,* 扩大或缩小时更新数组** @author hh* @date 2022-2-4 11:16*/
public class Solution {/*** 记录满足条件的最大长度*/private int maxLength = Integer.MIN_VALUE;public int longestSubstring(String s, int k) {int strLength = s.length();for(int enumNum = 1; enumNum <= 26; enumNum ++){//定义变量int winCharTypeNums = 0;int[] frequency = new int[26];for(int l = 0,r = 0; r < strLength; r++){//判断之前数组是否记录,没有记录则winCharTypeNums加1,否则只更新数组int rightCharIndex = s.charAt(r) - 'a';if(frequency[rightCharIndex] == 0){winCharTypeNums++;}frequency[rightCharIndex] ++;//检查更新满足条件的最大长度if(winCharTypeNums == enumNum){check(frequency,k);}//检测是否需要缩小窗口while (winCharTypeNums > enumNum){//获取左边界字符,判断是否需要更新winCharTypeNumsint leftCharIndex = s.charAt(l) - 'a';if(frequency[leftCharIndex] == 1){winCharTypeNums--;}frequency[leftCharIndex]--;l++;}}}return maxLength == Integer.MIN_VALUE ? 0 : maxLength;}private void check(int[] frequency,int k){int strLength = 0;for (int value : frequency) {if (value != 0 && value < k) {return;}strLength += value;}maxLength = Math.max(strLength,maxLength);}}
经典题目
438. 找到字符串中所有字母异位词 (一题三解)
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例 1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
示例 2:输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。
地址:438. 找到字符串中所有字母异位词
思路1:
根据题目要求,我们需要在字符串 s 寻找字符串 p的异位词。因为字符串 p 的异位词的长度一定与字符串 p 的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词
代码实现1
package com.algorithm.num438;import java.util.LinkedList;
import java.util.List;/*** 思路:滑动窗口,窗口大小固定,每次滑动时需比较是否为异位词** @author hh* @date 2022-2-4 17:39*/
public class Solution {public List<Integer> findAnagrams(String s, String p) {List<Integer> result = new LinkedList<>();int[] pat = new int[26];int[] text = new int[26];int strLength = s.length();for(char c : p.toCharArray()){pat[c - 'a']++;}for(int l = 0,r = 0; r < strLength; r++){text[s.charAt(r) - 'a'] ++;if(l + p.length() - 1 == r && this.isAnagrams(text,pat)){//检测是否为异位词result.add(l);}while (l + p.length() - 1 <= r){text[s.charAt(l) - 'a']--;l++;}}return result;}private boolean isAnagrams(int[] text,int[] pat){for(int i = 0; i < text.length; i++){if(text[i] != pat[i]){return false;}}return true;}
}
思路2
在方法一的基础上,我们不再分别统计滑动窗口和字符串 p 中每种字母的数量,而是统计滑动窗口和字符串 p中每种类型字母的差;并引入变量differ 来记录当前窗口与字符串 p 中数量不同类型的字母的,并在滑动窗口的过程中维护它。
在判断滑动窗口中每种字母的数量与字符串 p 中每种字母的数量是否相同时,只需要判断 differ 是否为零即可。
代码实现2
package com.algorithm.num438;import java.util.LinkedList;
import java.util.List;/*** @author hh* @date 2022-2-4 21:20*/
public class Solution2 {public List<Integer> findAnagrams(String s, String p) {List<Integer> result = new LinkedList<>();if(s.length() < p.length()){return result;}//维护窗口的差值int[] diffArr = new int[26];//diff表示字符类型不同的数量int diff = 0;//预处理for(int i = 0; i< p.length();i++){diffArr[p.charAt(i) - 'a']++;diffArr[s.charAt(i) - 'a']--;}for(int i = 0 ; i < 26; i++ ){if(diffArr[i] != 0){diff++;}}if(diff == 0){result.add(0);}for(int l = 0; l < s.length() - p.length(); l++){int leftIndex = s.charAt(l) - 'a';if(diffArr[leftIndex] == 0){diff++;}else if(diffArr[leftIndex] == -1){diff--;}diffArr[leftIndex]++;int rightIndex = s.charAt(l + p.length()) - 'a';if(diffArr[rightIndex] == 0){diff++;}else if(diffArr[rightIndex] == 1){diff--;}diffArr[rightIndex]--;if(diff == 0){result.add(l+1);}}return result;}
}
思路3
与思路1类似,本思路记录窗口中字母类型数量及词频和字符串p的字母类型数量及词频是否相等。
解法一中每次对滑动窗口的检查都不可避免需要检查两个词频数组,复杂度为 O(C)O(C)O(C)。
事实上,我们只关心两个数组是否完全一致,因而我们能够只维护一个词频数组 cnt 来实现。
起始处理 p 串时,只对 cnt 进行词频字符自增操作。当处理 s 的滑动窗口子串时,尝试对 cnt 中的词频进行「抵消/恢复」操作:
当滑动窗口的右端点右移时(增加字符),对 cnt 执行右端点字符的「抵消」操作;
当滑动窗口的左端点右移时(减少字符),对 cnt 执行左端点字符的「恢复」操作。
同时,使用变量 a统计 p 中不同字符的数量,使用变量 b统计滑动窗口(子串)内有多少个字符词频与 p 相等。
当滑动窗口移动( 执行「抵消/恢复」)时,如果「抵消」后该字符词频为 0,说明本次右端点右移,多产生了一位词频相同的字符;如果「恢复」后该字符词频数量为 1,说明少了一个为词频相同的字符。当且仅当 a = b 时,我们找到了一个新的异位组。
代码实现3
package com.algorithm.num438;import java.util.LinkedList;
import java.util.List;/*** @author hh* @date 2022-2-4 21:20*/
public class Solution3 {public List<Integer> findAnagrams(String s, String p) {List<Integer> result = new LinkedList<>();//维护窗口的差值int[] diffArr = new int[26];int strLength = s.length();int a = 0,b = 0;//预处理for(char c : p.toCharArray()){diffArr[c - 'a']++;}for(int i : diffArr){if(i > 0){a++;}}for(int l = 0,r = 0; r < strLength; r++){int rightIndex = s.charAt(r) - 'a';if(diffArr[rightIndex] == 1){//由不同变为相同b++;}diffArr[rightIndex]--;if(l + p.length() - 1 == r && a == b){//检测是否为异位词result.add(l);}while (l + p.length() - 1 <= r){int leftIndex = s.charAt(l) - 'a';if(diffArr [leftIndex] == 0){b--;}diffArr[leftIndex]++;l++;}}return result;}
}
其它练习题目
请读者自行练习
- 入门:424. 替换后的最长重复字符
- 经典:567. 字符串的排列
滑动窗口算法精讲(Sliding Window Algorithm)相关推荐
- ST算法 Sliding Window algorithm template
ST算法(Sliding Window):A easy way to slove the substring problems algorithm template to slove substrin ...
- 滑动窗口算法(Sliding Window Algorithm)
目录 滑动窗口算法(Sliding Window Algorithm) 一.基本示例 二.经典使用 1. 连续元素最大和 题目 解法及分析 2. 最长无重复子字符串 题目 解法及分析 三.参考资料 滑 ...
- Java算法-滑动窗口算法(Sliding Window)(十)
滑动窗口 滑动窗口概念不仅存在于数据链路层,也存在于传输层,两者有不同的协议,但基本原理是相近的.其中一个重要区别是,一个是针对于帧的传送,另一个是字节数据的传送. 滑动窗口(Sliding wind ...
- 从零开始学习VIO笔记 --- 第四讲:滑动窗口(基于滑动窗口算法的 VIO 系统:可观性和一致性)
从零开始学习VIO笔记 --- 第四讲:滑动窗口(基于滑动窗口算法的 VIO 系统:可观性和一致性) 一. 从高斯分布到信息矩阵 1.1 高斯分布 1.2 高斯分布和协方差矩阵 1.3 信息矩阵 二. ...
- c语言 滑窗法_滑动窗口算法(一)
某日事不多,点开sentinel-core代码学习,想看看qps.rt等是怎么统计的. 点开StatisticSlot类,发现里面是用DefaultNode增加qps,然后尝试点开 DefaultNo ...
- 滑动窗口算法基本原理与实践
原文作者:huansky 原文地址:滑动窗口算法基本原理与实践 目录 滑动窗口算法(Sliding Window Algorithm) 基本示例 滑动窗口法的大体框架 算法实例 1208. 尽可能使字 ...
- Java实现滑动窗口算法
Java实现滑动窗口算法 滑动窗口算法 滑动窗口应用实例 基本实例 断路器怎么识别断路器的状态 代码实现 滑动窗口算法 思想不滑坡 方法总比困难多 滑动窗口协议(Sliding Window Prot ...
- 列表左右箭头滑动_我写了一套框架,把滑动窗口算法变成了默写题
读完本文,你可以去力扣拿下如下题目: 76.最小覆盖子串 567.字符串的排列 438.找到字符串中所有字母异位词 3.无重复字符的最长子串 ----------- 鉴于前文 二分搜索框架详解 的那首 ...
- 滑动窗口算法通用思想
文章目录 一.最小覆盖子串 二.找到字符串中所有字母异位词 三.无重复字符的最长子串 最后总结 本文详解「滑动窗口」这种高级双指针技巧的算法框架,带你秒杀几道难度较大的子字符串匹配问题: 最小覆盖子串 ...
最新文章
- B-监狱逃脱(BFS)
- C语言中长度为0的数组
- Go gin获取GET请求参数
- java程序cpu突然飚高_Java 定位导致CPU飙升的代码过程
- AtomicInteger 的使用
- 华为P50系列确定29日发布:但遗憾的是...
- dlut-KFQ概率上机2
- VoIP之语音编码器
- NopCommerce支持友好路由
- 深度学习自学(二十六):人脸数据集
- 深度学习中网络在训练时不收敛的原因
- tomcat重启时出现:严重: Error filterStart
- 聊聊新西兰的工作和生活
- Python中的Counter类
- 关闭 C4996 警告(_CRT_SECURE_NO_DEPRECATE)方法
- matplotlib中文字体设置
- java程序模拟手机
- 祝大家2022幸福安康
- oppo手机刷linux,Ubuntu手机系统介绍及Ubuntu刷机教程分享
- SwiftUI Mapkit 导航基础教程大全之 MKMapView地图显示并实现导航线路(教程含源码)
热门文章
- 【LDA】LDA主题模型笔记—主要是狄利克雷
- 2、fixture机制
- linux服务器查看公网IP信息的方法
- android仿百度外卖波浪_iOS实现百度外卖头像波浪的效果
- word另存为html emz和wmz,轉載:如何將offcie 2003文檔(.doc、.xls、.ppt)轉換成mht文檔...
- VxWorks 操作系统简介
- echarts 之常用的属性配置
- java 日志的作用_java日志概述和原理
- MATLAB自带工具箱实现PCA降维代码
- 行业分析-全球与中国电源和充电器市场现状及未来发展趋势