个人觉得日常遇到的关于滑动窗口的算法题主要分两种:
固定窗口大小的滑动窗口
在固定窗口大小的滑动窗口问题中,窗口的大小是预先定义好的,不会改变。这种类型的问题是相对简单的,因为一旦确定了窗口的大小,就可以直接遍历数组或列表,每次移动窗口一个元素的位置。常见的问题包括:
- 最大/最小子数组和:给定一个数组和一个固定大小的窗口,找到所有可能的窗口的最大/最小和。
- 窗口内元素的统计:例如,统计窗口内奇数或偶数元素的数量。
- 窗口内元素的频率统计:统计窗口内每个元素出现的次数。
解决这类问题的关键在于维护窗口内的状态,以便在窗口滑动时能够快速更新状态。通常可以使用队列、双端队列(deque)或哈希表来维护窗口内的状态。
动态窗口大小的滑动窗口
动态窗口大小的滑动窗口问题则更为复杂,因为窗口的大小不是固定的,而是根据某些条件动态变化的。这类问题通常涉及到在窗口内寻找满足特定条件的子数组或子串。常见的问题包括:
- 无重复字符的最长子串:找到最长的不包含重复字符的子串。
- 包含所有字符的最短子串:给定一个字符串和一个字符集合,找到包含所有字符集合中字符的最短子串。
- 滑动窗口内的特定条件:例如,找到窗口内所有元素的乘积小于等于给定值的最大窗口。
解决这类问题的关键在于如何高效地扩展和收缩窗口,同时维护窗口内的状态。常用的数据结构包括:
- 双端队列(deque):用于维护窗口内元素的顺序,以便快速找到窗口的边界。
- 哈希表:用于快速查找和更新窗口内元素的频率。
- 前缀和/前缀最大/最小值:用于快速计算窗口内元素的和、最大值或最小值。
在解决动态窗口大小的滑动窗口问题时,通常需要维护一个滑动窗口的左右指针,并根据特定条件来移动这些指针。例如,当窗口内不满足条件时,移动左指针来缩小窗口;当窗口内满足条件但需要寻找更优解时,移动右指针来扩展窗口。一般常见的是右指针实现扩张,通过扩张不断找到符合要求的字符串,然后左指针实现收缩,寻求当前状态下的最小的边界。
下面关于动态的滑动窗口给出题目,感兴趣的可以看下(掘金链接):最小替换子串长度 - MarsCode
问题描述
小F得到了一个特殊的字符串,这个字符串只包含字符A
、S
、D
、F
,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得A
、S
、D
、F
这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
测试样例
样例1:
输入:
input = "ADDF"
输出:
1
样例2:
输入:
input = "ASAFASAFADDD"
输出:
3
样例3:
输入:
input = "SSDDFFFFAAAS"
输出:
1
样例4:
输入:
input = "AAAASSSSDDDDFFFF"
输出:
0
样例5:
输入:
input = "AAAADDDDAAAASSSS"
输出:4
求解代码:
java
public class Main {
public static int solution(String input) {
//这是一个很久之前遗留的问题,代码的第一步,清除我的原来的依托的代码,不得不否认,现在也是依托
if(input.length() < 4){
return input.length();
}
int[] target = get(input);
int[] window = new int[4];
int left = 0, right = 0;
int minLength = input.length();
while (right < input.length()) {
// 扩展窗口
char ch = input.charAt(right);
switch (ch) {
case 'A' -> window[0]++;
case 'S' -> window[1]++;
case 'D' -> window[2]++;
case 'F' -> window[3]++;
}
right++;
// 检查窗口是否满足条件
while (isValidWindow(window, target)) {
minLength = Math.min(minLength, right - left);
if(minLength == 0){
return 0;
}
// 缩小窗口
char leftChar = input.charAt(left);
switch (leftChar) {
case 'A' -> window[0]--;
case 'S' -> window[1]--;
case 'D' -> window[2]--;
case 'F' -> window[3]--;
}
left++;
}
}
return minLength;
}
public static boolean isValidWindow(int[] window, int[] target) {
for (int i = 0; i < 4; i++) {
if ( target[i] > 0 && window[i] < target[i]) {
return false;
}
}
return true;
}
public static int[] get(String input){
int len = input.length() / 4;
int[] result = new int[4];
for(int i = 0;i < input.length(); i++){
char ch = input.charAt(i);
switch (ch) {
case 'A' -> result[0]++;
case 'S' -> result[1]++;
case 'D' -> result[2]++;
case 'F' -> result[3]++;
}
}
for(int i = 0; i < 4; i++){
result[i] = result[i] - len;
}
return result;
}
public static void main(String[] args) {
// You can add more test cases here
System.out.println(solution("ADDF") == 1);
System.out.println(solution("ASAFASAFADDD") == 3);
}
}