滑动窗口法一般用来处理子数组长度的问题,也需要用到两个指针,通过两个指针的移动,来确定子数组的范围。由于两个指针在不停的移动,子数组的范围也在不停变化,类似于滑动的窗口,所以叫做滑动窗口法。废话少说,看题来理解。
第209题:
给定一个含有n个正整数的数组和一个正整数 target。 找出该数组中满足其和≥ target的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。
假定我们的target = 7, nums = [2,3,1,2,4,3]
首先定义左指针(left)和右指针(right)都为0,此时它们都指向nums[0]=2,明显小于target=7,不满足题意,此时右指针后移一位,现在子数组为:[2,3],它们的和为5,不满足题意,right继续右移,直到子数组为[2,3,1,2]时,子数组的和为8,满足题意。
但是我们需要找到长度最小的子数组,所以此时让left右移一位,看还是否满足条件,left右移后子数组为[3,1,2],小于target。right继续右移,找到[3,1,2,4],满足题意,left继续右移变为[1,2,4],仍旧满足题意,可是不能输出结果,万一后面有更短的呢?
left继续后移,子数组为[2,4]不满足题意,那么right后移,子数组为[2,4,3]满足,此时left后移,子数组为[4,3],终于,我们找到了满足题意的最短子数组[4,3]。
在这整个过程中,子数组在不断地变化,所以我们叫做滑动窗口法,代码实现如下:
public class question209 {
public int minSubArrayLen(int s, int[] nums) {
int left = 0;//定义左指针
int right;//定义右指针
int sum = 0;//定义子数组的和
int result = Integer.MAX_VALUE;//给最终结果赋值
for (right = 0;right < nums.length;right++) {
sum = sum + nums[right];
while (sum >= s) {
result = Math.min(result,right - left + 1);
sum = sum - nums[left];
left++;
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
第904题:
你正在探访一家农场,农场从左到右种植了一排果树。 这些树用一个整数数组fruits表示,其中fruits[i]是第i棵树上的水果种类 。 你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果: 你只有两个篮子,并且每个篮子只能装单一类型的水果。 每个篮子能够装的水果总量没有限制。 你可以选择任意一棵树开始采摘,你必须从每棵树(包括开始采摘的树)上恰好摘一个水果 。 采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。 给你一个整数数组 fruits,返回你可以收集的水果的最大数目。
警告!!!!不要被题目吓到!!认真,细致,转换为编程语言。
说白了,题目就是让找只包含两种元素的最长子数组!!!
这么想的话,比上一题多出一个步骤,就是需要暂时存储我们找到数字的数目,为此,我们创建一个新数组a。
拿fruits = [0,1,2,2]来举例:
首先取了一个0,那么a[0]的位置就加1,这个时候我们已经取了一个种类的水果,所以水果的种类count要加1;
接着取了一个1,a[1]的位置就加1,水果的种类count同样加1;
然后取2,a[2]的位置加1,种类count加1,可此时count=3,超出了题目要求,所以就舍弃最先取下来的0,这个时候a[0]的位置就要减1,count也对应减1,
接着取最后一个2,a[2]的位置再加1,变成2,此时count还是2,不变,最终找到了结果[1,2,2]
以下是代码实现部分:
public class question904 {
public int totalFruit(int[] fruits) {
int left = 0;
int right;
int total = 2;//收集水果的个数(最少也能拿两个)
int[] frequenceFruit = new int[fruits.length];//这个数组盛放:每一种水果拿了几次
int count = 0;//统计拿了几种水果
//优先考虑特殊情况:水果的种类小于等于两种
if (fruits.length <= 2) {
return fruits.length;
}
for (right = 0;right < fruits.length;right++) {
frequenceFruit[fruits[right]]++;//对应的一种水果的数目+1
if (frequenceFruit[fruits[right]] == 1) {
count++;//水果的种类+1
}
while (count > 2) {//篮子中水果的种类超过2
frequenceFruit[fruits[left]]--;
if (frequenceFruit[fruits[left]] == 0) {
count--;
}
left++;
}
total = Math.max(total,right - left + 1);//取2和子数组长度中最大的
}
return total;
}
}
最后一题,第76题:
给你一个字符串 s 、一个字符串 t 。 返回s中涵盖t所有字符的最小子串。 如果s中不存在涵盖t所有字符的子串,则返回空字符串 "" 。 注意: 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们保证它是唯一的答案
这题看上去不复杂,答案的思路我也能看的懂并且理解。可是代码实现真的好复杂,看着看着就晕了,我先把代码贴上来,以后慢慢参透:
public class question76 {
public String minWindow(String s, String t) {
if (s == null || s.length() == 0 || t == null || t.length() == 0){
return "";
}
int[] need = new int[128];
//记录需要的字符的个数
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}
//left是当前左边界,right是当前右边界,
//size记录窗口大小,count是需求的字符个数,start是最小覆盖串开始的index
int left = 0, right = 0, size = Integer.MAX_VALUE, count = t.length(), start = 0;
//遍历所有字符
while (right < s.length()) {
char c = s.charAt(right);
if (need[c] > 0) {//需要字符c
count--;
}
need[c]--;//把右边的字符加入窗口
if (count == 0) {//窗口中已经包含所有字符
while (left < right && need[s.charAt(left)] < 0) {
need[s.charAt(left)]++;//释放右边移动出窗口的字符
left++;//指针右移
}
if (right - left + 1 < size) {//不能右移时候挑战最小窗口大小,更新最小窗口开始的start
size = right - left + 1;
start = left;//记录下最小值时候的开始位置,最后返回覆盖串时候会用到
}
//l向右移动后窗口肯定不能满足了 重新开始循环
need[s.charAt(left)]++;
left++;
count++;
}
right++;
}
return size == Integer.MAX_VALUE ? "" : s.substring(start, start + size);
}
}