滑动窗口(sliding window)是一种常见的算法技巧,通常用于解决字符串、数组等数据结构相关的问题。
滑动窗口算法的基本思想是维护一个滑动窗口,该窗口通常是一个固定大小的子序列,并且该子序列通常是连续的。然后,通过滑动窗口的方式,不断地更新窗口的位置和大小,以便解决问题。
基本思路
具体而言,滑动窗口算法的常见步骤如下: 滑动窗口算法的思路:
- 在字符串 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缩小窗口,即移出字符时,应该更新哪些数据?
- 我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
常见应用
最小覆盖子串(Minimum Window Substring)
leetcode 76. 最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
public class Solution76 {
public String minWindow(String s, String t) {
char[] originChs = s.toCharArray(), destChs = t.toCharArray();
int originLen = originChs.length, destLen = t.length();
// 将char存入到数组作为hash
int[] hash = new int[128];
for (int i = 0; i < destLen; i++) {
hash[destChs[i]]--;
}
String result = "";
// 左闭右开,count是当前待符合条件长度
for (int left = 0, right = 0, count = 0; right < originLen; right++) {
hash[originChs[right]]++;
if (hash[originChs[right]] <= 0) {
count++;
}
// left缩小窗口条件
while (count == destLen && hash[originChs[left]] > 0) {
hash[originChs[left++]]--;
}
if (count == destLen) {
//符合最小覆盖子串条件
if (result.equals("") || result.length() > right + 1 - left) {
result = s.substring(left, right + 1);
}
}
}
return result;
}
@Test
public void testCase01() {
String s = "ADOBECODEBANC", t = "ABC";
String result = minWindow(s, t);
Assert.assertEquals(result, "BANC");
}
@Test
public void testCase02() {
String s = "a", t = "a";
String result = minWindow(s, t);
Assert.assertEquals(result, "a");
}
@Test
public void testCase03() {
String s = "a", t = "aa";
String result = minWindow(s, t);
Assert.assertEquals(result, "");
}
}
长度最小的子数组
leetcode 209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入: target = 7, nums = [2,3,1,2,4,3] 输出: 2 解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
public class Solution209 {
public int minSubArrayLen(int target, int[] nums) {
int len = nums.length, ret = 0;
for (int left = 0, right = 0, count = 0; right < len; right++) {
count += nums[right];
while (count >= target && count - nums[left] >= target) {
count -= nums[left++];
}
if (count >= target) {
if (ret ==0 || right - left + 1 < ret) {
ret = right + 1 - left;
}
}
}
return ret;
}
@Test
public void testCase01() {
int result = minSubArrayLen(7, new int[]{2, 3, 1, 2, 4, 3});
Assert.assertEquals(result, 2);
}
@Test
public void testCase02() {
int result = minSubArrayLen(4, new int[]{1, 4, 4});
Assert.assertEquals(result, 1);
}
@Test
public void testCase03() {
int result = minSubArrayLen(11, new int[]{1, 1, 1, 1, 1, 1, 1, 1});
Assert.assertEquals(result, 0);
}
}
其他算法题
leetcode 438. 找到字符串中所有字母异位词
leetcode 3. 无重复字符的最长子串
leetcode 674. 最长连续递增序列
开源软件应用
Sentinel滑动窗口
Sentinel 是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度来维护系统的稳定性。
滑动窗口核心实现类LeapArray
//具体实现代码和应用场景待分析