难度参考
难度:简单
分类:数组
难度与分类由我所参与的培训课程提供,但需要注意的是,难度与分类仅供参考。以下内容均为个人笔记,旨在督促自己认真学习。
题目
给定一个含有个正整数的数组和一个正整数s,找出该数组中满足其和≥s的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0。
示例1:
输入:s=7,
nums=[2,3,1,2,4,3]
输出:2
解释:子数组[4,3]是该条件下的长度最小的子数组。
思路
暴力做法
使用暴力法解决这道题的思路是遍历所有可能的连续子数组,计算它们的和,并找到满足条件的最小子数组长度。以下是暴力法的详细题解:
-
初始化一些变量,包括最小长度
minLength
初始为正无穷大。 -
使用两层循环,外层循环以每个元素为起点,内层循环遍历从该起点开始的子数组。外层循环变量
start
从0开始,内层循环变量end
从start
开始。 -
在内层循环中,计算子数组的和
sum
,即从nums[start]
到nums[end]
的元素的累加和。 -
如果
sum
大于或等于目标值s
,说明当前子数组的和满足条件,可以记录下当前子数组的长度end - start + 1
。 -
在外层循环中,不断更新
minLength
,即记录当前满足条件的子数组的最小长度。 -
继续外层循环,直到遍历完整个数组。
-
最后,如果
minLength
没有被更新过,说明没有满足条件的子数组,返回0;否则,返回minLength
。
这个算法的核心思想是遍历所有可能的子数组,计算它们的和并比较长度,找到最小长度的满足条件的子数组。由于使用了两层循环,时间复杂度是O(n^2),其中n是数组的长度。这个算法虽然不如滑动窗口法高效,但是可以解决问题。
暴力做法不再提供示例与梳理,感觉可以直接看代码。
滑动窗口
可以使用滑动窗口的方法解决这个问题。滑动窗口是维护一个连续子数组的常用技巧,通过左指针和右指针来移动窗口,根据窗口内元素的和来调整窗口的大小。具体步骤如下:
-
初始化左指针
left
为0,右指针right
为0,以及窗口内元素的和sum
为0。 -
使用右指针
right
向右遍历数组,不断将元素添加到窗口内,并更新sum
。 -
当
sum
大于等于给定的正整数s
时,记录当前窗口的长度right - left + 1
。 -
缩小窗口,即左指针
left
向右移动,同时从sum
中减去左边界的元素,直到sum
小于s
。 -
重复步骤2到4,直到遍历完整个数组。
-
在整个过程中,不断更新最小子数组的长度,最终得到最小长度。
通过滑动窗口找到最小长度的连续子数组,时间复杂度为O(n),其中n是数组的长度。
示例
理解滑动窗口算法可能有点抽象,让我尝试以更简单的方式解释它。
简单解释:
滑动窗口算法就像你在一堆连续的数字中寻找一个连续的子集,这个子集的和大于等于给定的值s,而且这个子集的长度要尽可能小。
首先,你从数组的开头找到一个子集,看它的和是否满足条件。如果和小于s,你就继续扩大子集,添加更多的数字。如果和大于等于s,你记录下这个子集的长度。
接下来,你缩小子集的范围,从左边开始移除数字,然后再检查新的子集是否满足条件。如果满足,你再次记录子集的长度,然后继续缩小范围。
你不断地重复这个过程,直到遍历完整个数组。最终,你会找到一个满足条件的子集,它的长度是最小的。
这就是滑动窗口算法的核心思想:不断调整子集的范围,以找到满足条件的最小子集。
让我们使用一个示例来说明滑动窗口算法的工作方式:
示例:
假设有一个数组 nums
,其内容如下:
nums = [2, 3, 1, 2, 4, 3]
我们的目标是找到一个连续的子数组,该子数组的和大于等于7,并且长度尽可能小。
步骤1:初始化窗口
我们从左到右遍历数组,初始化左指针 left
和右指针 right
,以及窗口内的和 sum
:
left = 0, right = 0, sum = 0
步骤2:扩展窗口
我们开始扩展窗口,将右指针 right
向右移动,逐个添加元素,并更新 sum
的值。我们的目标是找到一个子数组,其和大于等于7。
left = 0, right = 0, sum = 2
left = 0, right = 1, sum = 5
left = 0, right = 2, sum = 6
left = 0, right = 3, sum = 8
在这个过程中,当 sum
大于等于7时,我们记录下当前窗口的长度(right - left + 1
),并且这是我们找到的目前最小的长度。
步骤3:缩小窗口
接下来,我们需要缩小窗口,即将左指针 left
向右移动,同时从 sum
中减去左边界的元素。我们不断缩小窗口,以尝试找到更小的子数组。
left = 1, right = 3, sum = 7
在这一步,我们找到了一个和为7的子数组,长度为3,这是目前找到的最小长度。
步骤4:继续寻找
然后,我们继续向右移动右指针 right
,并尝试寻找更小的子数组。
left = 1, right = 4, sum = 11
在这一步,我们找到了一个和为11的子数组,长度为4。
步骤5:缩小窗口
接着,我们再次缩小窗口,继续寻找更小的子数组。
left = 2, right = 4, sum = 9
在这一步,我们找到了一个和为9的子数组,长度为3。
步骤6:继续寻找
我们继续向右移动右指针 right
,寻找更小的子数组。
left = 2, right = 5, sum = 12
在这一步,我们找到了一个和为12的子数组,长度为4。
步骤7:缩小窗口
最后,我们再次缩小窗口。
left = 3, right = 5, sum = 10
在这一步,我们找到了一个和为10的子数组,长度为3。
图示:
2+3+1+2=8>7(找出该数组中满足其和≥s的长度),第一次更新滑动窗口长度。
尝试缩小窗口(移动左指针),发现3+1+2=6<7。
因此,继续寻找(移动右指针),调整窗口(1+2+4>7),第二次更新滑动窗口长度。
同理,在尝试缩小窗口(移动左指针【先】)与继续寻找(移动右指针【后】)之后,调整窗口(1+2+4>7),第三次更新滑动窗口长度。
尝试缩小窗口(移动左指针),发现4+3>=7,第四次更新滑动窗口长度。
尝试缩小窗口(移动左指针),发现3<7, 继续寻找(移动右指针), 右指针 j > 数组长度,结束循环。我们就得到了所需要的窗口长度(即该数组中满足其和≥s的长度最小的连续子数组的长度)。
结果:
在整个过程中,我们不断调整窗口的大小,以找到和大于等于7的最小子数组。最终,我们找到了一个和为7的子数组,长度为2。这是我们要找的答案。
所以,滑动窗口算法的结果是2,表示最小连续子数组的长度为2,即子数组 [4, 3]
。
梳理
滑动窗口算法之所以能够实现找到满足条件的最小连续子数组,是因为它巧妙地利用了窗口的概念,通过不断调整窗口的大小和位置,来搜索满足条件的最小子数组。以下是为什么这个算法能够实现的原因:
-
窗口的左右边界移动: 算法使用两个指针,一个左指针和一个右指针,它们分别表示当前窗口的左边界和右边界。通过不断移动这两个指针,算法模拟了不同窗口的情况。
-
窗口内元素和的计算: 算法维护一个变量
sum
,用于记录当前窗口内元素的和。随着右指针的移动,不断将新元素添加到窗口内,并更新sum
。这使得算法能够动态地计算窗口内元素的和。 -
根据和的大小调整窗口: 在每一步中,算法检查
sum
是否满足给定的条件(例如,是否大于等于s)。如果满足条件,算法会记录当前窗口的长度,然后尝试缩小窗口,即移动左指针。如果不满足条件,算法会继续扩大窗口,即移动右指针。 -
不断更新最小长度: 算法在整个过程中不断记录最小的子数组长度。每当找到一个满足条件的子数组时,它会与之前记录的最小长度比较,然后更新最小长度。这确保了算法找到的是最小的满足条件的子数组。
-
遍历整个数组: 算法通过不断移动右指针,遍历整个数组,以寻找满足条件的子数组。因为算法考虑了数组中的每个元素,所以它能够找到所有可能的子数组,从中选择最小长度的子数组。
总结来说,滑动窗口算法通过动态地维护一个窗口,根据窗口内元素和的大小来调整窗口的位置和大小,从而找到满足条件的最小子数组。它的核心思想是不断地搜索可能的子数组,然后选择最小长度的子数组作为答案。这个算法的时间复杂度为O(n),因为每个元素最多被访问两次(一次添加到窗口,一次从窗口移除),其中n是数组的长度。
代码
暴力做法
#include <iostream>
#include <vector>
#include <climits> // 包含 <climits> 头文件以引入 INT_MAX
using namespace std;
// 定义一个函数,找到满足和≥s的最短连续子数组的长度(暴力法)
int minSubArrayLen(int s, vector<int>& nums) {
int n = nums.size(); // 获取数组的大小
int minLength = INT_MAX; // 初始化最小长度为最大整数
for (int start = 0; start < n; start++) { // 以每个元素为起点
int sum = 0; // 定义当前子数组的和
for (int end = start; end < n; end++) { // 从起点开始遍历子数组
sum += nums[end]; // 向子数组内添加元素
if (sum >= s) { // 如果子数组的和满足条件
minLength = min(minLength, end - start + 1); // 更新最小长度
break; // 退出内层循环,继续下一个起点
}
}
}
// 如果minLength没有被更新,说明没有满足条件的子数组,返回0;否则返回最小长度
return minLength == INT_MAX ? 0 : minLength;
}
int main() {
int s = 7; // 给定的正整数s
vector<int> nums = {2, 3, 1, 2, 4, 3}; // 给定的正整数数组
// 调用函数找到满足条件的最短连续子数组的长度(暴力法)
int result = minSubArrayLen(s, nums);
cout << "最小连续子数组的长度为:" << result << endl; // 输出结果
return 0;
}
时间复杂度:O(n^2)
空间复杂度:O(1)
滑动窗口
#include <iostream>
#include <vector>
#include <climits> // 包含 <climits> 头文件以引入 INT_MAX
using namespace std;
// 定义一个函数,找到满足和≥s的最短连续子数组的长度
int minSubArrayLen(int s, vector<int>& nums) {
int n = nums.size(); // 获取数组的大小
int minLength = INT_MAX; // 初始化最小长度为最大整数
int left = 0; // 定义左指针
int sum = 0; // 定义当前窗口内元素的和
for (int right = 0; right < n; right++) { // 使用右指针遍历数组
sum += nums[right]; // 向窗口内添加一个元素
while (sum >= s) { // 当窗口内元素和大于等于s时
minLength = min(minLength, right - left + 1); // 更新最小长度
sum -= nums[left]; // 缩小窗口,左指针向右移动
left++; // 左指针向右移动
}
}
// 如果minLength没有被更新,说明没有满足条件的子数组,返回0;否则返回最小长度
return minLength == INT_MAX ? 0 : minLength;
}
int main() {
int s = 7; // 给定的正整数s
vector<int> nums = {2, 3, 1, 2, 4, 3}; // 给定的正整数数组
// 调用函数找到满足条件的最短连续子数组的长度
int result = minSubArrayLen(s, nums);
cout << "最小连续子数组的长度为:" << result << endl; // 输出结果
return 0;
}
时间复杂度:O(n)
空间复杂度:O(1)
打卡
暴力做法打卡
滑动窗口打卡