题目
先用一道leetcode算法题目引出本文算法,题目链接是209:
暴力解法
这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,毫无疑问,时间复杂度很明显是O(n^2) 。
代码
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int result = Integer.MAX_VALUE; // 最终的结果
int sum = 0; // 子序列的数值之和
int subLength = 0; // 子序列的长度
for (int i = 0; i < nums.length; i++) { // 设置子序列起点为i
sum = 0;
for (int j = i; j < nums.length; j++) { // 设置子序列终止位置为j
sum += nums[j];
if (sum >= target) { // 一旦发现子序列和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength;
break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == Integer.MAX_VALUE ? 0 : result;
}
}
复制代码
复杂度
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
滑动窗口算法
滑动窗口算法,其实就是不断地调节子序列的起始位置和终止位置,从而得出要想的结果,以本题目为例:
在前后两个指针的移动过程中,会找到如下几个满足条件的窗口: 【2,3,1,2】, 【3,1,2,4】,【1,2,4】,【2,4,3】,【4,3】 最终【4,3】为满足解题条件的最优解
滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。窗口的起始位置如何移动: 如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。解题的关键在于 窗口的起始位置如何移动,如图所示:
滑动窗口的难点和精髓就在于在找到满足条件的窗口之后,前后两个指针怎么移动:
while (sum >= target) {
//计算满足条件的窗口长度
subLength = Math.min(subLength, right - left + 1);
//这里其实也可以分开写,sum先减去左边指针的值,然后左边指针往右移动
sum -= array[left++];
}
}
复制代码
完整代码
class Solution {
public int minSubArrayLen(int target, int[] array) {
int length = array.length; int subLength = length + 1;
int sum = 0; int left = 0;
for (int right = 0; right < length; right++) {
sum += array[right];
while (sum >= target) {
subLength = Math.min(subLength, right - left + 1);
sum -= array[left++];
}
}
return subLength > length ? 0 : subLength;
}
}
复制代码
复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
【参考】