什么是滑动窗口(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++;
// 进行窗口内数据的一系列更新
...
}
}
}

滑动窗口算法的思路:

  1. 在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0 ,把索引左闭右开区间 [left, right) 称为一个「窗口」。
  2. 不断地增加 right 指针扩大窗口 [left, right) ,直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
  3. 此时停止增加 right ,转而不断增加 left 指针缩小窗口 [left, right) ,直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left ,都要更新一轮结果。
  4. 重复第2和第3步,直到 right 到达字符串 S 的尽头。

needswindow 相当于计数器,分别记录 T 中字符出现次数和「窗口」中的相应字符的出现次数。

开始套模板之前,要思考以下四个问题:

  1. 当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
  2. 什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
  3. 当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
  4. 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

滑动窗口问题实例

最小覆盖子串

LeetCode题目:76.最小覆盖子串

1、阅读且分析题目

题目中包含关键字:时间复杂度O(n)字符串最小子串。可使用滑动窗口算法解决。

2. 思考滑动窗口算法四个问题

1、当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?

更新 window 中加入字符的个数,判断 needwindow 中的字符个数是否相等,相等则 valid++

2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?

window 包含 need 中的字符及个数时,即 valid == len(need)

3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?

更新 window 中移出字符的个数,且判断 needwindow 中的移出字符个数是否相等,相等则 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 中加入字符的个数,判断 needwindow 中的字符个数是否相等,相等则 valid++

2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?

window 包含 need 中的字符及个数时,即 valid == len(need)

3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?

更新 window 中移出字符的个数,且判断 needwindow 中的移出字符个数是否相等,相等则 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 中加入字符的个数,判断 needwindow 中的字符个数是否相等,相等则 valid++

2、什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?

window 包含 need 中的字符及个数时,即 valid == len(need)

3、当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?

更新 window 中移出字符的个数,且判断 needwindow 中的移出字符个数是否相等,相等则 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值等关键字时,可使用滑动窗口算法。
  • 模板中的向左和向右时的处理是对称的。
  • 套模板前思考四个问题:
    1. 当移动 right 扩大窗口,即加入字符时,应该更新哪些数据?
    2. 什么条件下,窗口应该暂停扩大,开始移动_left_ 缩小窗口?
    3. 当移动 left 缩小窗口,即移出字符时,应该更新哪些数据?
    4. 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?

参考资料

滑动窗口(Sliding Window)技巧总结的更多相关文章

  1. LeetCode编程训练 - 滑动窗口(Sliding Window)

    滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...

  2. 算法与数据结构基础 - 滑动窗口(Sliding Window)

    滑动窗口基础 滑动窗口常用来解决求字符串子串问题,借助map和计数器,其能在O(n)时间复杂度求子串问题.滑动窗口和双指针(Two pointers)有些类似,可以理解为往同一个方向走的双指针.常用滑 ...

  3. TCP滑动窗口Sliding Window

    滑动窗口的发送窗口示意图如下,其中由对端通告的窗口窗口大小为6,窗口中和窗口外的数据分别表示为:1-3发送并已经被确认的数据段,4-6发送但尚未被确认的数据段,7-9能够发送尚未发送的数据段,10-… ...

  4. [POJ2823][洛谷P1886]滑动窗口 Sliding Window

    题目大意:有一列数,和一个窗口,一次能框连续的s个数,初始时窗口在左端,不断往右移动,移到最右端为止,求每次被框住的s个数中的最小数和最大数. 解题思路:这道题是一道区间查询问题,可以用线段树做.每个 ...

  5. [Leetcode 3] 最长不重复子串 Longest substring without repeating 滑动窗口

    [题目] Given a string, find the length of the longest substring without repeating characters. [举例] Exa ...

  6. 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 ...

  7. 一维滑动窗口(SlidingWindow)

    滑动窗口(Sliding Window)问题经常使用快慢指针(slow, fast pointer)[0, slow) 的区域为滑动窗口已经探索过的区域[slow, fast]的区域为滑动窗口正在探索 ...

  8. Storm Windowing storm滑动窗口简介

    Storm Windowing 简介 Storm可同时处理窗口内的所有tuple.窗口可以从时间或数量上来划分,由如下两个因素决定: 窗口的长度,可以是时间间隔或Tuple数量: 滑动间隔(slidi ...

  9. [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 ...

随机推荐

  1. mit-6.828 Lab Tools

    Lab Tools 目录 Lab Tools 写在前面 GDB GNU GPL (通用公共许可证) QEMU ELF 可执行文件的格式 Verbose mode Makefile 写在前面 操作系统小 ...

  2. iOS苹果美区 Apple ID 账号最新注册教程,iPhone用户务必收藏!

    编の语 前言 今天杀手宝宝出一个注册美区ID的教程,这是目前注册苹果美区ID最快的方法,所有人适合使用! 提の示 温馨提示: 所有内容均免费分享,部分资源来自于 网络,如与版权问题联系宝宝处理! 知道 ...

  3. 面试必问的volatile关键字

    原文: 卡巴拉的树   https://juejin.im/post/5a2b53b7f265da432a7b821c 在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度, ...

  4. 解决IIS发布时CS0016未能写入输出文件错误

    今天遇到一个将asp.net项目部署到IIS后访问的时候报的一个错误: 在网上查询了相关资料后,解决方法如下: 找到C:\Windows\下的temp文件,右键属性>安全>编辑,给其中II ...

  5. Improving RGB-D SLAM in dynamic environments: A motion removal approach

    一.贡献 (1)提出一种针对RGB-D的新的运动分割算法 (2)运动分割采用矢量量化深度图像 (3)数据集测试,并建立RGB-D SLAM系统 二.Related work [1]R.K. Namde ...

  6. 036_go语言中的原子计数器

    代码演示 package main import ( "fmt" "runtime" "sync/atomic" "time&qu ...

  7. 003_go语言中的变量

    代码演示: package main import "fmt" func main() { var a = "initial" fmt.Println(a) v ...

  8. 同一台服务器部署三个Tomcat

    工具 apache-tomcat-8.0.30 安装tomcat步骤           1. 下载apache-tomcat-8.0.30 ,下载下来的文件为apache-tomcat-8.0.30 ...

  9. Linux 中文编码

  10. 从零搭建Spring Boot脚手架(3):集成mybatis

    1. 前言 今天继续搭建我们的kono Spring Boot脚手架,上一文集成了一些基础的功能,比如统一返回体.统一异常处理.快速类型转换.参数校验等常用必备功能,并编写了一些单元测试进行验证,今天 ...