滑动窗口三步走:
第一步确定尾指针++的条件,往右扩张;第二步确定头指针++的条件,往右收缩,第三步更新所求目标值(一般都是极值)
滑动窗口伪代码:
start = 0
end = 0 //初始化
while(扩张条件)
{
while(收缩条件)
{
FindTarget()//收缩时一般求极小值
start++//收缩,有可能是++,也有可能是跳跃式移动
}
FindTarget()//扩张时一般求极大值
end++//扩张,一般都是++
}
if(一次都没进入过收缩条件)
{
特殊处理一下
}
目前来看,滑动窗口主要有两种题型,一种是找最长/最短子数组,这种使用上述的三步走策略。另一种是固定长度的窗口内的极值,此时需要借助单调队列
题目一:209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
思路:
for(遍历整个字符串)
{
for(和值大于target)
{
计算一次最短长度
头指针右移收缩
}
尾指针右移扩张
}
没收缩过需要特殊处理
代码:
int minSubArrayLen(int target, int* nums, int numsSize)
{
int start = 0, end = 0;
int sum = 0, minlen = INT_MAX, tmp = 0;
while (end < numsSize)
{
sum += nums[end];
while (sum >= target)
{
tmp = end - start + 1;
if (minlen > tmp)
{
minlen = tmp;
}
sum -= nums[start];
start++;
}
end++;
}
return minlen == INT_MAX ? 0 : minlen;
}
题目二:3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
思路:
for(遍历字符串)
{
for(出现了重复字符)
{
头指针右移內缩,右移到重复字符后面一位的地方,保证新的串口内没有重复字符
}
尾指针右移扩张
每次扩张计算一次最大距离
}
代码:
int lengthOfLongestSubstring(char * s){
int start = 0, end = 0, len = 0, output = 0;
int chs[128] = {0};
if (0 == strlen(s) || NULL == s)
{
return 0;
}
for (end = 0; s[end] != '\0'; end++)
{
//难点1:chs[s[end]]内保存的是目标字符最近一次出现的位置,方便头指针右移找位置
if (chs[s[end]] > start)
{
//难点1相关处理
start = chs[s[end]];
//目标是求最长距离,它更可能出现在扩张的过程中
//本题收缩时计算和扩张时计算完全重复,则可以省略
//len = end - start + 1;
//output = output < len ? len : output;
}
//难点1相关处理
chs[s[end]] = end + 1;
len = end - start + 1;
output = output < len ? len : output;
}
return output;
}
题目三:76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
思路:
for(遍历整个字符串)
{
for(s中出现了t中的所有元素)
{
计算最小覆盖长度
头指针右移收缩
}
尾指针右移扩张
}
代码:
char * minWindow(char * s, char * t){
int start = 0, end = 0;
int slen = strlen(s);
int tlen = strlen(t);
int chst[128] = {0};
int minlen = INT_MAX, minlenindex;
int tmplen;
//统计t中所有字符出现的次数
for (end = 0; end < tlen; end++)
{
chst[t[end]]++;
}
end = 0;
while (end < slen)
{
if (chst[s[end]] > 0)
{
//此时tlen的大小代表s已经出现了(strlen(t) - tlen)个t中的元素
tlen--;
}
//chst[i]为正,代表s中还需出现chst[i]次字符i,才会使s与t中出现i的次数相同
//chst[i]为负,代表字符i只在s中出现
chst[s[end]]--;
//难点:tlen==0 时代表s中已经出现了t中所有的元素(包括重复的元素)
//同时,也作为头指针收缩的条件
while (0 == tlen)
{
//更新最小覆盖距离
tmplen = end - start + 1;
if (minlen > tmplen)
{
minlen = tmplen;
minlenindex = start;
}
chst[s[start]]++;
if (chst[s[start]] > 0)
{
//代表收缩后的窗口不满足条件了,需要扩张
tlen++;
}
start++;
}
end++;
}
//收缩过至少一次
if(minlen != INT_MAX)
{
char* t = (char*)malloc(sizeof(char)*(minlen+1));
*t = '\0';
strncat(t, s+minlenindex, minlen);
return t;
}
//一次都没收缩过
return "";
}
题目四:239. 滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
思路:滑动窗口找极值,采用单调队列思想
//由于窗口长度被固定了,所以没有动态的收缩扩张过程
for(维护滑动窗口的移动,直到遍历结束)
{
维护一个单调队列,保证队头指针指向的元素一定是窗口内的最大值,且整个队列保持从左到右单调递减
对于每一次窗口向前移动,对应删除一个旧元素,增加一个新的元素(必须先判断出队,再判断入队)
每移动一次就把队头元素输出一次
}
//难点1:判断出队的元素是不是队头元素
//难点2:队头元素出队后如何使队头指针指向新的队头
//难点3:入队的元素找到位置后直接覆盖旧元素,可以省略数组插入新元素的过程
代码:
typedef struct Queue
{
int head;
int tail;
int * data;
}MyQueue;
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize)
{
int* output = malloc(sizeof(int) * numsSize);
MyQueue q;
int i, j = 0;
q.head = 0;
q.tail = 0;
q.data = malloc(sizeof(int) * numsSize);
*returnSize = 0;
for (i = 0; i < numsSize; i++)
{
//data内存的是数组下标,而非数组元素,目的是方便队头元素出队后,队头指针head容易找到下一个队头
//data[head]处于窗口外了(被出队了),更新队头指针
if (q.data[q.head] <= i - k && q.head < q.tail)
{
q.head++; // 出队后的下一个元素就是最大的
}
//给新入队的元素找在单调队列中的位置(在队尾指针指向的位置直接覆盖)
for (j = q.tail; j > q.head; j--)
{
if (nums[q.data[j - 1]] < nums[i])
{
q.tail--;
}
//这里用for循环会超时的原因
//1. 多给j进行了++操作
//2. 如果不加beark,每次会进行多余的循环
else
{
break;
}
}
#if 0
if (q.tail > q.head)
{
while (q.tail > q.head && nums[q.data[q.tail - 1]] < nums[i])
{
q.tail--;
}
}
#endif
q.data[q.tail++] = i;
//前k个元素不输出(等待窗口成型)
if (i >= k - 1)
{
output[(*returnSize)++] = nums[q.data[q.head]];
}
}
return output;
}