滑动窗口(Sliding Window)技巧总结
什么是滑动窗口(Sliding Window)
The Sliding Problem contains a sliding window which is a sub – list that runs over a Large Array which is an underlying collection of elements.
滑动窗口算法可以用以解决数组/字符串的子元素问题,它可以将嵌套的循环问题,转换为单循环问题,降低时间复杂度。
比如找最长的全为1的子数组长度。滑动窗口一般从第一个元素开始,一直往右边一个一个元素挪动。当然了,根据题目要求,我们可能有固定窗口大小的情况,也有窗口的大小变化的情况。
如何判断使用滑动窗口算法
如果题目中求的结果有以下情况时可使用滑动窗口算法:
- 最小值 Minimum value
- 最大值 Maximum value
- 最长值 Longest value
- 最短值 Shortest value
- K值 K-sized value
算法模板与思路
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
滑动窗口算法的思路:
- 在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0 ,把索引左闭右开区间 [left, right) 称为一个「窗口」。
- 不断地增加 right 指针扩大窗口 [left, right) ,直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
- 此时停止增加 right ,转而不断增加 left 指针缩小窗口 [left, right) ,直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left ,都要更新一轮结果。
- 重复第2和第3步,直到 right 到达字符串 S 的尽头。
needs 和 window 相当于计数器,分别记录 T 中字符出现次数和「窗口」中的相应字符的出现次数。
开始套模板之前,要思考以下四个问题:
- 当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
- 什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
- 当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
- 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
滑动窗口问题实例
最小覆盖子串
LeetCode题目:76.最小覆盖子串
1、阅读且分析题目
题目中包含关键字:时间复杂度O(n)、字符串、最小子串。可使用滑动窗口算法解决。
2. 思考滑动窗口算法四个问题
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
更新 window 中加入字符的个数,判断 need 与 window 中的字符个数是否相等,相等则 valid++ 。
2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
当 window 包含 need 中的字符及个数时,即 valid == len(need) 。
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
更新 window 中移出字符的个数,且判断 need 与 window 中的移出字符个数是否相等,相等则 valid-- 。
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
在缩小窗口时,因为求的是最小子串。
3. 代码实现
func minWindow(s string, t string) string {
need, window := make(map[byte]int), make(map[byte]int)
for i := 0; i < len(t); i++ { // 初始化 need
if _, ok := need[t[i]]; ok {
need[t[i]]++
} else {
need[t[i]] = 1
}
}
left, right, valid := 0, 0, 0
start, slen := 0, len(s)+1 // 设置长度为 len(s) + 1 表示此时没有符合条件的子串
for right < len(s) { // 滑动窗口向右扩大
c := s[right]
right++
if _, ok := need[c]; ok { // 向右扩大时,更新数据
if _, ok := window[c]; ok {
window[c]++
} else {
window[c] = 1
}
if window[c] == need[c] {
valid++
}
}
for valid == len(need) { // 当窗口包括 need 中所有字符及个数时,缩小窗口
if right-left < slen { // 缩小前,判断是否最小子串
start = left
slen = right - left
}
d := s[left]
left++
if v, ok := need[d]; ok { // 向左缩小时,更新数据
if window[d] == v {
valid--
}
window[d]--
}
}
}
if slen == len(s)+1 { // 长度 len(s) + 1 表示此时没有符合条件的子串
return ""
} else {
return s[start : start+slen]
}
}
4. 复杂度分析
- 时间复杂度:O(n),n 表示字符串 s 的长度。遍历一次字符串。
- 空间复杂度:O(m),m 表示字符串 t 的长度。使用了两个哈希表,保存字符串 t 中的字符个数。
字符串排列
LeetCode题目:567.字符串的排列
1、阅读且分析题目
题目中包含关键字:字符串、子串,且求 s2 中是否包含 s1 的排列,即求是否包含长度 k 的子串。可使用滑动窗口算法解决。
2. 思考滑动窗口算法四个问题
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
更新 window 中加入字符的个数,判断 need 与 window 中的字符个数是否相等,相等则 valid++ 。
2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
当 window 包含 need 中的字符及个数时,即 valid == len(need) 。
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
更新 window 中移出字符的个数,且判断 need 与 window 中的移出字符个数是否相等,相等则 valid-- 。
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
无论在扩大时或缩小窗口时都可以,因为求的是固定长度的子串。选择在缩小窗口时更新。
3. 代码实现
func checkInclusion(s1 string, s2 string) bool {
if s1 == s2 {
return true
}
need, window := make(map[byte]int), make(map[byte]int)
for i := 0; i < len(s1); i++ {
if _, ok := need[s1[i]]; ok {
need[s1[i]]++
} else {
need[s1[i]] = 1
}
}
left, right := 0, 0
valid := 0
for right < len(s2) {
c := s2[right]
right++
if _, ok := need[c]; ok {
if _, ok := window[c]; ok {
window[c]++
} else {
window[c] = 1
}
if window[c] == need[c] {
valid++
}
}
for valid == len(need) {
if right-left == len(s1) {
return true
}
d := s2[left]
left++
if _, ok := need[d]; ok {
if _, ok := window[d]; ok {
if window[d] == need[d] {
valid--
}
window[d]--
}
}
}
}
return false
}
4. 复杂度分析
- 时间复杂度:O(n),n 表示字符串 s2 的长度。遍历一次字符串。
- 空间复杂度:O(m),m 表示字符串 s1 的长度。使用了两个哈希表,保存字符串 s1 中的字符个数。
找所有字母异位词
LeetCode题目:438.找到字符串中所有字母异位词
1、阅读且分析题目
题目中包含关键字:字符串,且求 s 中的所有 p 的字母异位词的子串。可使用滑动窗口算法解决。
2. 思考滑动窗口算法四个问题
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
更新 window 中加入字符的个数,判断 need 与 window 中的字符个数是否相等,相等则 valid++ 。
2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
当 window 包含 need 中的字符及个数时,即 valid == len(need) 。
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
更新 window 中移出字符的个数,且判断 need 与 window 中的移出字符个数是否相等,相等则 valid-- 。
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
无论在扩大时或缩小窗口时都可以,因为求的是固定长度的子串。选择在缩小窗口时更新。
3. 代码实现
func findAnagrams(s string, p string) []int {
need, window := make(map[byte]int), make(map[byte]int)
for i := 0; i < len(p); i++ { // 初始化
if _, ok := need[p[i]]; ok {
need[p[i]]++
} else {
need[p[i]] = 1
}
}
left, right := 0, 0
valid := 0
ans := make([]int, 0)
for right < len(s) {
c := s[right]
right++
if _, ok := need[c]; ok {
if _, ok := window[c]; ok {
window[c]++
} else {
window[c] = 1
}
if need[c] == window[c] {
valid++
}
}
for valid == len(need) {
if right-left == len(p) {
ans = append(ans, left)
}
d := s[left]
left++
if _, ok := need[d]; ok {
if _, ok := window[d]; ok {
if need[d] == window[d] {
valid--
}
window[d]--
}
}
}
}
return ans
}
4. 复杂度分析
- 时间复杂度:O(n),n 表示字符串 s 的长度。遍历一次字符串。
- 空间复杂度:O(m),m 表示字符串 p 的长度。使用了两个哈希表,保存字符串 p 中的字符个数。
最长无重复子串
LeetCode题目:3. 无重复字符的最长子串
1、阅读且分析题目
题目中包含关键字:时间复杂度O(n)、字符串、最小子串。可使用滑动窗口算法解决。
2. 思考滑动窗口算法四个问题
1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
更新 window 中加入字符的个数,及当 window 中的某个字符个数 == 2时,更新 valid == false 。
2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
当 window 中的字符及个数 == 2时,即 valid == false 。
3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
更新 window 中移出字符的个数,且判断 window 中移出字符个数是否 == 2 ,相等则 valid == true 。
4、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
在扩大窗口时,因为求的是最大子串。
3. 代码实现
func lengthOfLongestSubstring(s string) int {
if s == "" { // 当字符串为空时,返回0
return 0
}
window := make(map[byte]int)
left, right, max := 0, 0, 0
valid := true
for right < len(s) {
c := s[right]
right++
if _, ok := window[c]; !ok { // 初始化
window[c] = 0
}
window[c]++ // 累加
if window[c] == 2 { // 当出现重复字符时
valid = false
} else { // 否则累加不重复子串长度,并且判断是否当前最长
if max < right-left {
max = right - left
}
}
for valid == false {
d := s[left]
left++
if window[d] == 2 {
valid = true
}
window[d]--
}
}
return max
}
4. 复杂度分析
- 时间复杂度:O(n),n 表示字符串 s 的长度。遍历一次字符串。
- 空间复杂度:O(n),n 表示字符串 s 的长度。使用了哈希表,保存不重复的字符个数。
总结
- 滑动窗口算法可以用以解决数组/字符串的子元素问题,它可以将嵌套的循环问题,转换为单循环问题,降低时间复杂度。
- 问题中包含字符串子元素、最大值、最小值、最长、最短、K值等关键字时,可使用滑动窗口算法。
- 模板中的向左和向右时的处理是对称的。
- 套模板前思考四个问题:
- 当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
- 什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
- 当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
- 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
参考资料
滑动窗口(Sliding Window)技巧总结的更多相关文章
- LeetCode编程训练 - 滑动窗口(Sliding Window)
滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...
- 算法与数据结构基础 - 滑动窗口(Sliding Window)
滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...
- TCP滑动窗口Sliding Window
滑动窗口的发送窗口示意图如下,其中由对端通告的窗口窗口大小为6,窗口中和窗口外的数据分别表示为:1-3发送并已经被确认的数据段,4-6发送但尚未被确认的数据段,7-9能够发送尚未发送的数据段,10-… ...
- [POJ2823][洛谷P1886]滑动窗口 Sliding Window
题目大意:有一列数,和一个窗口,一次能框连续的s个数,初始时窗口在左端,不断往右移动,移到最右端为止,求每次被框住的s个数中的最小数和最大数. 解题思路:这道题是一道区间查询问题,可以用线段树做.每个 ...
- [Leetcode 3] 最长不重复子串 Longest substring without repeating 滑动窗口
[题目] Given a string, find the length of the longest substring without repeating characters. [举例] Exa ...
- POJ 2823 滑动窗口 单调队列模板
我们从最简单的问题开始: 给定一个长度为N的整数数列a(i),i=0,1,...,N-1和窗长度k. 要求: f(i) = max{a(i-k+1),a(i-k+2),..., a(i)},i = 0 ...
- 一维滑动窗口(SlidingWindow)
滑动窗口(Sliding Window)问题经常使用快慢指针(slow, fast pointer)[0, slow) 的区域为滑动窗口已经探索过的区域[slow, fast]的区域为滑动窗口正在探索 ...
- Storm Windowing storm滑动窗口简介
Storm Windowing 简介 Storm可同时处理窗口内的所有tuple.窗口可以从时间或数量上来划分,由如下两个因素决定: 窗口的长度,可以是时间间隔或Tuple数量: 滑动间隔(slidi ...
- [LeetCode] Sliding Window Maximum 滑动窗口最大值
Given an array nums, there is a sliding window of size k which is moving from the very left of the a ...
随机推荐
- jmeter分布式踩得坑汇总
一.普通的配置文件基本都能网上搜索资料,这里就简单记录: a.jmeter.properties几处修改:1.remote_hosts=master压力机Ip;2.server_port,开启服务器端 ...
- 使用Azure人脸API对图片进行人脸识别
人脸识别是人工智能机器学习比较成熟的一个领域.人脸识别已经应用到了很多生产场景.比如生物认证,人脸考勤,人流监控等场景.对于很多中小功能由于技术门槛问题很难自己实现人脸识别的算法.Azure人脸API ...
- Hexo小技巧(包括如何插入本地图片)
我在研究如何在Hexo中引用本地图片时,看到官方文档对此问题已给出了解决方法,并亲测有效.当然,我并不满足于仅仅知道这一个技巧.在大致阅读过官方文档后,我总结了之前我个人并不知道的几个关于Hexo写博 ...
- Linux系统安装MySQL详细教程
首先进入MySQL官网下载rpm安装包 用yum install mysql80-community-release-el7-3.noarch.rpm 安装 yum repolist all|grep ...
- 申请支付宝app支付签约综合评分不足,拒绝不通过快速强开通支付宝App支付强开,强开支付宝App支付产品权限!
一.如何开通支付宝App支付 正常来说,按照官方的指引要求填写相关资料,即可开通支付宝手机网站支付.但是,更多的时候我们的申请都会碰到一些阻力,常见的阻力就是“系统综合评估签约条件不满足,谢谢您的支持 ...
- java web应用启动报错:Several ports (8080, 8009) required by Tomcat v6.0 Server at localhost are already in use.
Several ports (8080, 8009) required by Tomcat v6.0 Server at localhost are already in use. The serve ...
- 2020-07-29:从 innodb 的索引结构分析,为什么索引的 key 长度不能太长?
福哥答案2020-07-29: key 太长会导致一个页当中能够存放的 key 的数目变少,间接导致索引树的页数目变多,索引层次增加,从而影响整体查询变更的效率. 索引字段大小限制关于innodb_l ...
- C#LeetCode刷题之#350-两个数组的交集 II(Intersection of Two Arrays II)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4044 访问. 给定两个数组,编写一个函数来计算它们的交集. 输入 ...
- My_Tomcat_Host 靶机
1:扫描网段: 发现主机IP为192.168.1.203 2:nmap 扫描端口信息 发现8080端口开启了http服务 22ssh服务 3:尝试ssh连接是需要密码的,然后访问8080端口 4:发 ...
- MySQL设置传输包大小
MySQL执行插入或更新时, 当数据量过大时, 可能由于"max_allowed_packet"参数的限制导致执行失败.此时, 可以重新设置该参数的值. "max_all ...