题目链接
题目描述
给定一个含有
n
n
n个正整数的数组和一个正整数target 。
找出该数组中满足其总和大于等于
t
a
r
g
e
t
target
target的长度最小的子数组
[
n
u
m
s
l
,
n
u
m
s
l
+
1
,
.
.
.
,
n
u
m
s
r
−
1
,
n
u
m
s
r
]
[numsl, numsl+1, ..., numsr-1, numsr]
[numsl,numsl+1,...,numsr−1,numsr],并返回其长度。如果不存在符合条件的子数组,返回
0
0
0。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
- 1 <= target <= 109
- 1 <= nums.length <= 105
- 1 <= nums[i] <= 105
进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
解题思路
子数组的概念
子数组是数组中的一个连续部分,包含原数组中的若干连续元素。子数组与原数组共享相同的元素顺序,但是子数组的长度可以变化,从包含原数组的一个元素到包含原数组的所有元素不等。
注意:
- 子数组的长度可以是 1 1 1(单个元素)到原来数组的长度之间的任何值
- 子数组一定是原数组中的一个连续部分,比如,对于数组 n u m s nums nums [ a , b , c , d ] [a, b, c, d] [a,b,c,d],数组 [ a ] [a] [a]、 [ a , b ] [a,b] [a,b]、 [ b , c ] [b,c] [b,c]、 [ a , b , c ] [a,b,c] [a,b,c]等都是 n u m s nums nums的子数组,而像 [ a , c ] [a,c] [a,c], [ a , c , d ] [a,c,d] [a,c,d]等这样的数组就不能作为 n u m s nums nums的子数组。
- 对于一个长度为 n n n的数组,其子数组的个数为 n ∗ ( n + 1 ) / 2 n*(n+1)/2 n∗(n+1)/2。( n + ( n − 1 ) + . . . + 2 + 1 n+(n-1)+...+2+1 n+(n−1)+...+2+1)
循环解法
使用两个
f
o
r
for
for循环来确定满足条件的子数组的区间,将所有的子数组都访问一遍,外层
f
o
r
for
for循环确定子数组的起点,内层
f
o
r
for
for循环确定子数组的终点。
代码展示:
class Solution {
int minSubArrayLen(int target, vector<int>& nums) {
// 定义最小的子数组的长度,这里只要保证它能够比所有子数组可能的长度都大就行
// 子数组的长度最大就是nums.size(),所以我直接取nums.size() + 1
int minLength = nums.size() + 1;
// 子数组的起点在原数组中的索引i
for (int i = 0; i < nums.size(); i++) {
// 定义sum用来保存子数组的和
int sum = 0;
// 定义子数组的长度
int length = 0;
// 子数组的终点在原数组中的索引j
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
// 子数组每新增一个元素,长度加1
length++;
// 找到目标,即第一次出现子数组的和大于等于目标值,更新最小长度
if (sum >= target) {
if (minLength > count)
minLength = count;
// 一旦符合条件就break,因为要求找的是最小的长度
break;
}
}
}
// 如果没找到符合要求的子数组,则返回值为0.即要将minLength修改成0
if (minLength > nums.size())
minLength = 0;
return minLength;
}
};
时间复杂度: O ( n 2 ) O(n^2) O(n2)
滑动窗口
滑动窗口算法概述
滑动窗口是一种高效的数据处理技术,主要用于数组和字符串相关问题,通过减少循环的嵌套来降低算法的时间复杂度。
- 定义:滑动窗口时在一个特定大小的字符串或数组上进行操作的一种技术,通过双指针调整窗口的起始和结束位置来进行计算和优化,从而在时间复杂度上达到 O ( n ) O(n) O(n)的高效性能。
- 工作原理:滑动窗口的实现主要依赖于双指针或者迭代器来标识当前处理的子序列(窗口)。在数组或字符串的处理中,窗口逐步移动,然后再每个位置执行相应的操作,比如,该题就是计算窗口中所有元素的和并和募兵制进行比较。
- 应用场景:滑动窗口算法经常用于解决涉及到子字符串或者子数组的问题。
基本思路:
- 初始化窗口
初始化左右边界 l e f t = r i g h t = 0 left=right=0 left=right=0,把索引区间 [ l e f t , r i g h t ] [left, right] [left,right]称为一个窗口。 - 寻找可行解
先不断增加 r i g h t right right指针,不断扩大窗口,直到窗口中出现满足条件的可行解。 - 优化可行解
找到可行解后,停止增加 r i g h t right right,不断增加 l e f t left left指针缩小窗口 [ l e f t , r i g h t ] [left,right] [left,right],直到窗口中的可行解不再满足条件。同时,没增加一次 l e f t left left,就要对结果进行一次更新,保证结果的准确性。 - 滑动窗口,直到一次遍历结束
不断重复 2 2 2和 3 3 3,直到 r i g h t right right走到最后。
图片来源:卡哥 代码随想录
代码展示:
class Solution {
int minSubArrayLen(int target, vector<int>& nums) {
// 定义left用来记录左边界
int left = 0;
// 记录子数组的和
int sum = 0;
// 记录返回值,即满足条件的子数组的最小长度
int result = INT_MAX;
// 定义right用来记录右边界,定义我们的窗口
for (int right = 0; right < nums.size(); right++) {
// 对子数组进行求和
sum += nums[right];
// 出现子数组的和大于等于target时,缩小窗口
while (sum >= target) {
sum -= nums[left];
// 记录每一次的子数组的长度
int len = right - left + 1;
// 更新结果,保证结果的准确性
result = result > len ? len : result;
left++;
}
}
// 如果没有任何一个子数组满足条件,则result的值应该为0
return result > nums.size() ? 0 : result;
}
};
时间复杂度 O ( n ) O(n) O(n)
注意: f o r for for循环里面的循环变量(代码中的 r i g h t right right)只能是子数组的终止位置,如果是起始位置,那就和暴力循环一样了。
总结
滑动窗口算法以其独特的双指针技术和窗口调整机制,为解决一系列复杂的子数组或子串的问题提供了一种高效且直观的方法。不仅在算法竞赛和数据科学领域中有着广泛的应用,在网络通信领域也扮演着重要的角色。通过有效的窗口管理和数据控制,可以显著提高数据处理效率和网络传输的稳定性。
这是我第一次接触滑动窗口,如果有表达不清楚或者有误的地方,欢迎各位读者指出,我也会做出相应的修改。
本文参考学习视频:拿下滑动窗口! | LeetCode 209 长度最小的子数组