滑动窗口
- 滑动窗口常见做题思路:
- 定义需要维护的变量们(对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
- 定义窗口的首尾端(start, end),然后滑动窗口
- 更新需要维护的变量, 有的变量需要一个if语句来维护(比如最大最小长度)
- Step 4 - 情况1
//如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度
//如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1 定义的(部分或所有) 维护变量 - Step 4 - 情况2
//如果题目的窗口长度可变:这个时候一般涉及到窗口是否合法的问题
//如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
//在左指针移动之前更新Step 1 定义的(部分或所有) 维护变量
- 代码实现:
public int problemName(String s) {
//Step 1:定义需要维护的变量们(对于滑动窗口类题目,这些变量通常是最小长度,最大长度,或者哈希表)
int x = ……;
int y = ……;
//Step 2:定义窗口的首尾端(start, end),然后滑动窗口
int start = 0;
int len = s.length();
for (int end = 0; end < len; end++) {
//Step 3:更新需要维护的变量, 有的变量需要一个if语句来维护(比如最大最小长度)
x = new_x;
if (condition) {
y = new_y;
}
//------------下面是两种情况,读者请根据题意二选1------------//
//Step 4 - 情况1
//如果题目的窗口长度固定:用一个if语句判断一下当前窗口长度是否超过限定长度
//如果超过了,窗口左指针前移一个单位保证窗口长度固定, 在那之前, 先更新Step 1 定义的(部分或所有) 维护变量
if (窗口长度大于限定值) {
//更新(部分或所有) 维护变量
//窗口左指针前移一个单位保证窗口长度固定
}
//Step 4 - 情况2
//如果题目的窗口长度可变:这个时候一般涉及到窗口是否合法的问题
//如果当前窗口不合法时, 用一个while去不断移动窗口左指针, 从而剔除非法元素直到窗口再次合法
//在左指针移动之前更新Step 1 定义的(部分或所有) 维护变量
while (不合法) {
//更新(部分或所有) 维护变量
//不断移动窗口左指针直到窗口再次合法
}
}
//Step 5:返回答案
return ...;
}
159. 至多包含两个不同字符的最长子串
给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t ,并返回该子串的长度。
示例 1:
输入: “eceba”
输出: 3
解释: t 是 “ece”,长度为3。
示例 2:
输入: “ccaabbb”
输出: 5
解释: t 是 “aabbb”,长度为5。
思路:
实际上这道题也是双指针的思想。
- 一个指针指向开头,一个指向末尾
- 当子字符串中含有的字符种类<=2,就更新end增加字符串长度
- 当子字符串中含有的字符种类 > 2,就更新start减少字符串长度
- 用一个max_len记录最长子字符串
- 用一个HashMap记录整个子字符串的字符种类不能超过2
代码实现:
class Solution {
public int lengthOfLongestSubstringTwoDistinct(String s) {
int len = s.length();
if (len < 3) {
return len;
}
int max_len = 0;
Map<Character, Integer> map = new HashMap<>();//map用来记录子字符串的情况
char[] ch = s.toCharArray();
int start = 0;
for (int end = 0; end < len; end++) {
char tail = ch[end];
if (map.containsKey(tail)) {
map.put(tail, map.get(tail) + 1);//记录该字符的最大长度
} else {
map.put(tail, 1);//初次见面
}
if (map.size() <= 2) {//更新max_len
max_len = Math.max(max_len, end - start + 1);
}
while (map.size() > 2) {//更新start
char head = ch[start];
if (map.containsKey(head)) {
map.put(head, map.get(head) - 1);
}
if (map.get(head) == 0) {
map.remove(head);
}
start++;//往后移动
}
}
return max_len;
}
}
340. 至多包含 K 个不同字符的最长子串
给定一个字符串 s ,找出 至多 包含 k 个不同字符的最长子串 T。
示例 1:
输入: s = “eceba”, k = 2
输出: 3
解释: 则 T 为 “ece”,所以长度为 3。
示例 2:
输入: s = “aa”, k = 1
输出: 2
解释: 则 T 为 “aa”,所以长度为 2。
思路:
跟上面那道题一样的,就是把2变成k
实现代码:
class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
int len = s.length();
if (len < k+1) {
return len;
}
int max_len = 0;
Map<Character, Integer> map = new HashMap<>();//map用来记录子字符串的情况
char[] ch = s.toCharArray();
int start = 0;
for (int end = 0; end < len; end++) {
char tail = ch[end];
if (map.containsKey(tail)) {
map.put(tail, map.get(tail) + 1);//记录该字符的最大长度
} else {
map.put(tail, 1);//初次见面
}
if (map.size() <= k) {//更新max_len
max_len = Math.max(max_len, end - start + 1);
}
while (map.size() > k) {//更新start
char head = ch[start];
if (map.containsKey(head)) {
map.put(head, map.get(head) - 1);
}
if (map.get(head) == 0) {
map.remove(head);
}
start++;//往后移动
}
}
return max_len;
}
}
1004. 最大连续1的个数 III
给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
思路:
其实我个人觉得,滑动窗口其实就是一种双指针,一个指左,一个指右。某个条件下左指针移动,某个条件右指针移动。然后用一个int res,动态的记录最值。
- 左指针
- 右指针
- 左指针移动前是0,k++
- 右指针移动前是0,k–
- 当左指针或者右指针遍历到最后一个元素时结束
实现代码
public static int longestOnes(int[] nums, int k) {
int len = nums.length;
int res = 0;
int i = 0, j = 0;
while (i < len && j < len) {
if (nums[j] != 0 || k != 0) {
if (nums[j++] == 0) {
k--;
}
} else {
if (nums[i++] == 0) {
k++;
}
}
res = res > j - i ? res : j - i;
}
return res;
}
239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:
输入:nums = [1], k = 1
输出:[1]
思路:
维护一个长度为K的单调列,然后不停得往后遍历,拿出一个同时将不在视野内的都删除
代码实现:
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>((pair1, pair2) -> pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1]);
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{nums[i], i});//前k个
}
//排好序了
int[] ans = new int[n - k + 1];
ans[0] = pq.peek()[0];
for (int i = k; i < n; ++i) {
pq.offer(new int[]{nums[i], i});//加入一个
while (pq.peek()[1] <= i - k) {//将所有的不在视野内的东西都删除
pq.poll();
}
ans[i - k + 1] = pq.peek()[0];
}
return ans;
}
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路:
很简单,也是双指针的思路。一个快指针在后面遍历,一个慢指针在前面用来缩减字符串。具体思路如下
但是你会发现如此方法能做出来,但是时间复杂度很高。
仔细阅读题目可以发现,字符串都是大小写的英文字母,因此可以采用对字符串的统计数组来简化计算。(实际上思想还是上面的思想)
代码实现
public static String minWindow(String s, String t) {
char[] s1 = s.toCharArray();//将s字符串转化为字符数组
char[] t1 = t.toCharArray();//将t字符串转化为字符数组
int n = s1.length;//n记录s字符串的长度
int m = t1.length;//m记录t字符串的长度
int[] arr = new int[58];//z:122 A:65 122-65+1=58//对字符的个数统计使用的数组,如:0就是A即'A'-'A'
int fast = 0, slow = 0, count = 0;//定义快指针fast,慢指针slow,记录符合的字符个数count
String res = "";//定义返回的结果字符串
//---------先遍历t字符串-----------//
for (int i = 0; i < m; i++) {//首先将t中的字符,全部减一
arr[t1[i] - 'A']--;
}
//---------再遍历s字符串---------//
for (fast = 0; fast < n; fast++) {//滑动窗口快指针遍历
arr[s1[fast] - 'A']++;//将快指针遍历过的字符全部加1,表示经过了
if (arr[s1[fast] - 'A'] <= 0) {//如果是目标字符,则前面已经减一过了,所以只会<=0
count++;//记录符合目标字符的个数
}
//发现符合情况的字符串,此时我需要将字符串缩减到最短以满足要求
if (count == m) {//如果目标字符的个数等于t字符串的长度就开始慢指针遍历
while (arr[s1[slow] - 'A'] > 0) {//如果慢指针指向的数>0则不是目标字符,就向前移动
arr[s1[slow] - 'A']--;//因为已经脱离了快慢指针的范围就不统计个数了,于是就对1该字符个数减一
slow++;//慢指针移动
}
if ("" == res || res.length() > fast - slow + 1) {//因为满足目标字符的条件,就开始截取字符串了
res = s.substring(slow, fast + 1);//截取函数左闭右开,所以要fast+1
}
}
}
return res;//返回对应截取最短的字符串!
}