滑动窗口算法
记录labuladong的算法小抄,网站地址
1、概述
滑动窗口的核心是利用两个指针,维护一个窗口,不断滑动,时间复杂度o(N);
重点:
- 向窗口中添加新的元素
- 缩小窗口
- 窗口滑动的哪个阶段更新结果
- Integer和String不能直接用==进行相等判断,需要用equals方法
框架:
// 滑动窗口算法框架
void slidingWindow(String s) {
HashMap<Character, Integer> window;
int left = 0, right = 0;
while (right < s.length()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
// 判断左侧窗口是否要收缩
while (window needs shrink){
// d 是将移出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
2、最小覆盖字串
力扣第76题,最小覆盖字串,hard级别
滑动窗口思路如下:
- 采用左闭右开区间[left,right),方便使用==substring()==方法
- [0,0):没有元素
- [0,1):含有元素0
- 先不断增加right指针,直到窗口[left,right)中包含目标字符串(找到一个可行解)
- 此时,停止增加right,不断增加left,缩小窗口[left,right),直到窗口的字符串不再符合要求时更新新的一轮结果(优化可行解,找到最优解)
- 重复步骤2-3,直至right到达尽头
本题关键点:
- ASCII表总共有128个元素,初始两个数组分别作为字符串s和t的记录
- 右滑后再去进行滑动完的操作
- 先判断left,操作完毕后,进行左滑
代码:
[76]最小覆盖子串
class Solution {
public String minWindow(String s, String t) {
// 先行条件判断
if (s.length() < t.length()) {
return "";
}
int[] window = new int[128];// 记录滑动窗口中字符出现的次数
int[] need = new int[128];// 记录t中所有字符的出现的次数
for (char c : t.toCharArray()) {
need[c]++;
}
String result = "";// 返回的结果
int left = 0;// 左指针
int right = 0;// 右指针
int count = 0;// 滑动窗口中包含字符串t字符的个数
int minLength = s.length() + 1;// 返回结果的字符串的长度
// 左闭右开区间[left,right)
while (right < s.length()) {
char cur = s.charAt(right);
right++;
// 窗口右滑扩大做的操作
window[cur]++;
if (need[cur] > 0 && need[cur] >= window[cur]) {
count++;
}
// 窗口缩减的情况
while (count == t.length()) {
cur = s.charAt(left);
// 先判断当前left位置处的元素
// 判断count是否需要变小
if (need[cur] > 0 && need[cur] >= window[cur]) {
count--;
window[cur]--;
} else {
window[cur]--;
}
// 更新结果
if (right - left < minLength) {
result = s.substring(left, right);
minLength = right - left;
}
// 判断完毕后进行左滑
left++;
}
}
return result;
}
}
3、字符串排列
力扣第567题,字符串的排列
[567]字符串的排列
class Solution {
public boolean checkInclusion(String s1, String s2) {
int[] window = new int[128];
int[] need = new int[128];
// 将s1的字符串遍历放入need中
for (char c : s1.toCharArray()) {
need[c]++;
}
int count = 0;
// 定义left和right指针,组成滑动窗口
int left = 0, right = 0;
while (right < s2.length()) {
char cur = s2.charAt(right);
right++;
// 右滑后的操作
window[cur]++;
if (need[cur] > 0 && need[cur] >= window[cur]) {
count++;
}
// 收缩窗口的时机
while (count == s1.length()) {
// 判断,即此时[left,right)为s1
if (right - left == count) {
return true;
}
cur = s2.charAt(left);
// 判断是否需要缩小count
if (need[cur] > 0 && need[cur] >= window[cur]) {
count--;
window[cur]--;
} else {
window[cur]--;
}
left++;
}
}
// 结果false
return false;
}
}
4、找所有字母异位词
力扣438题,找到字符串中所有字母异位词
[438]找到字符串中所有字母异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
// 结果
List<Integer> result = new ArrayList<>();
// 两个数组
int[] window = new int[26];
int[] need = new int[26];
for (char c : p.toCharArray()) {
need[c - 'a']++;
}
// 指针和计数器
int left = 0, right = 0, count = 0;
// 开始窗口滑动
while (right < s.length()) {
char cur = s.charAt(right);
right++;
// 右滑后的操作
window[cur - 'a']++;
if (need[cur - 'a'] > 0 && need[cur - 'a'] >= window[cur - 'a']) {
count++;// 判断计数器是否需要更新
}
// 满足条件时
while (count == p.length()) {
// 处理结果
if (right - left == count) {
result.add(left);
}
// 窗口左滑
cur = s.charAt(left);
if (need[cur - 'a'] > 0 && need[cur - 'a'] >= window[cur - 'a']) {
count--;
window[cur - 'a']--;
} else {
window[cur - 'a']--;
}
left++;
}
}
// 返回结果集
return result;
}
}
5、最长无重复子串
力扣第3题,无重复字符的最长子串
[3]无重复字符的最长子串
class Solution {
public int lengthOfLongestSubstring(String s) {
int result = 0;// 结果
int[] window = new int[128];// 滑动的窗口
int left = 0, right = 0;
while (right < s.length()) {
char rightChar = s.charAt(right);
right++;
// 窗口右滑后的操作
window[rightChar]++;
if (window[rightChar] <= 1) {
result = right - left > result ? right - left : result;// 更新结果
}
// 满足条件时
while (window[rightChar] > 1) {
char leftChar = s.charAt(left);
window[leftChar]--;
left++;
}
}
return result;
}
}