力扣题目209:长度最小的子数组
应用背景
滑动窗口法常用于寻找子数组的题目,其基本思想与双指针法类似。
暴力解法
采用两个循环,第一个循环指向子串的起始位置,第二个循环指向子串的终止位置,通过两个循环不断寻找满足条件的子串;一旦找到满足条件的子串便进行更新,包括子串起始位置的更新和结果的更新。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = nums.size() + 1;
for (int i = 0; i != nums.size(); ++i) {
int sum = 0;
for (int j = i; j != nums.size(); ++j) {
sum += nums[j];
if (sum >= target) {
result = result > j - i + 1 ? j - i + 1 : result;
break;
}
}
}
return result == nums.size() + 1 ? 0 : result;
}
};
该解法的时间复杂度为O(n^2),在力扣中提交时发现该解法已经超时了。
滑动窗口法
滑动窗口法将暴力解法中的两个循环减少至一个循环,该循环指向子串的终止位置(因为如果该循环指向子串的起始位置,那么就和暴力解法没有什么区别了)。
对于起始位置该如何更新呢?
在本题中实现滑动窗口,主要需要确定如下几点:
1)窗口内是什么?
2)如何移动窗口的起始位置?
3)如何移动窗口的结束位置?
窗口内就是满足条件>=target的最小连续子数组;当窗口内的值满足条件时就意味着需要更新起始位置了,也就是需要将窗口内的值缩小了,以达到最小子串的目的,也就是重新不满足条件;而循环中索引所指向的是子串的终止位置,当不满足条件时,便一直向后移动。
解该题的关键就是实现子串起始位置的移动。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = nums.size() + 1;
int i = 0;//指向起始位置
int sum = 0;
for (int j = 0; j != nums.size(); ++j) {
sum += nums[j];
while (sum >= target) {
result = result > j - i + 1 ? j - i + 1 : result;
sum -= nums[i];
++i;
}
}
return result == nums.size() + 1 ? 0 : result;
}
};
如上,当满足条件时,对result和sum进行更新,并将起始位置向后移动。(此处需要使用while,因为起始位置需要移动直至窗口内的值不满足条件,然后再进行下一次循环,也就是移动终止位置的指针)。其实也就是只要满足条件值就需要移动起始位置的指针,也是该题目的关键如何移动起始位置指针。
力扣题目904:水果成蓝
该题目翻译为人话就是在一个数组中寻找满足条件的子数组。该条件为仅包含两种元素且子串长度为所有满足条件的子串中长度最长的。
基于上述分析还是一个寻找子数组的题目,所以适合采用滑动窗口法去解决。
滑动窗口法还是三个问题:
1)窗口内是什么?
2)如何移动窗口的起始位置?
3)如何移动窗口的结束位置?
窗口内是满足条件起始位置到终止位置中内只存在两种元素,且窗口内包含的元素数目最多;起始位置是当窗口内的元素不满足条件时进行移动,使得窗口内的元素重新满足条件;而循环中索引所指向的是子串的终止位置,当满足条件时,便一直向后移动。
该题的关键仍是起始位置的移动。如何将起始位置进行合适的移动。
class Solution {
public:
int totalFruit(vector<int>& fruits) {
//当容器长度小于3时,直接返回容器的的大小
if (fruits.size() < 3)
return fruits.size();
int result = 0;
int i = 0;
int j = 1;
int first = fruits[i];
int second = fruits[j];
++j;
while (j != fruits.size()) {
if (fruits[j] == first || fruits[j] == second) {
result = result > j - i + 1 ? result : j - i + 1;
++j;
}
else {
i = j - 1;
first = fruits[i];
second = fruits[j];
while (i > 0 && fruits[i - 1] == first)
--i;
result = result > j - i + 1 ? result : j - i + 1;
++j;
}
}
return result;
}
};
如上,当窗口内的元素满足条件,继续移动终止位置;当不满足条件时,进行起始位置的更新,使得窗口内的元素重新满足条件。因为此时终止位置指向的元素使得窗口内的元素不满足条件,所以其一定与它的前一个元素不等(因为在此之前窗口内只存在两种元素,在加入它以后存在了三种元素),所以此时满足条件的窗口应该包含终止位置的元素和它的前一个元素;但是为了使得窗口包含的元素最多,所以需要在满足条件的前提下将起始位置向前移动。改题目的关键仍是如何合适的移动起始位置。
209题和904题的区别与联系
区别
209是需要寻找满足条件的最短子数组,而904 是需要寻找满足条件的最长子数组。所以209题是在满足条件时进行起始位置的移动,直至不满足条件;不满足条件时则继续移动终止位置直至满足条件;并在满足条件时记录下子数组的长度,找到最短的子数组。904题则是在不满足条件时进行起始位置的移动,使得窗口内的元素重新满足条件,满足条件时则继续移动终止位置。
联系
两个题目均属于滑动窗口法的应用。在应用时都是需要考虑滑动窗口法的三个问题。