个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创滑动窗口(1)_长度最小的子数组
收录于专栏【经典算法练习】
本专栏旨在分享学习C++的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. 题目链接:
OJ链接-----长度最小的子数组
2. 题目描述 :
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的
子数组
[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回 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 <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
3. 解法 :
解法一(暴力枚举) :
算法思路 :
[从前往后]枚举数组中的任意一个元素,把它当成起始位置.然后从这个[起始位置]开始,寻找一段最短的区间,使得这段区间的和[大于等于]目标值.
将所有元素作为起始位置所得的结果中,找到[最小值]即可.
具体步骤:
- 用i, j两层循环遍历完整个数组
- 用sum统计[i, j]区间之和
- 如果sum >= target, 说明j没有移动的必要(数组内全是正整数)就让i移动
- 移动前更新一下最小长度len
代码展示 :
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = INT_MAX, n = nums.size();
for (int i = 0; i < n; i++)
{
int sum = 0;
for (int j = i; j < n; j++)
{
sum += nums[j];
if (sum >= target)
{
len = min(len, j - i + 1);
break;
}
}
}
return len == INT_MAX ? 0 : len;
}
};
结果分析 :
还是一样,分析题目给的数据范围:
题目给的数据范围:
1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
也就是说我们数组中的数的个数是在[0, 10^5]之内,而我们的暴力算法时间复杂度为O(N^2),
数据级别为10^10 > 10^9,所以会超出时间限制!!!
解法二(滑动窗口) :
滑动窗口简介
滑动窗口是一种在处理序列(如数组或字符串)问题时的算法技术。它通过定义一个“窗口”在序列上滑动,以动态调整和计算子序列的属性,而无需重新计算整个序列。窗口的大小可以是固定的,也可以是动态调整的。
基本步骤:
初始化窗口:设置窗口的起始位置。
扩展窗口:根据问题需求,扩展窗口(例如,右移)。
更新窗口:在窗口范围内执行所需操作(如求和、计数等)。
缩小窗口:根据条件(如窗口太大),调整窗口(例如,左移)。
重复步骤:直到窗口遍历完整个序列。
这种方法提高了效率,尤其是在需要处理大量数据时。
算法思路 :
由于此问题分析的对象是[一段连续的区间], 因此可以考虑[滑动窗口]的思想来解决这道题.
让滑动窗口满足: 从i位置开始,窗口内所有元素的和小于target(那么当窗口内元素之和第一次大于等于目标值的时候,就是i位置开始,满足条件的最小长度).
做法: 将右端元素划入窗口中,统计出此时窗口内元素的和:
如果窗口内元素之和大于等于target: 更新结果,并且将左端元素划出去的同时继续判断是否满足条件并更新结果(因为左端元素可能性很小,划出去之后依旧满足条件)
如果窗口内的元素之和不满足条件: rihgt++,另下一个元素进入窗口.
图解流程 :
代码展示 :
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = INT_MAX, n = nums.size(), sum = 0;
for(int right = 0, left = 0; right < n; right++)
{
sum += nums[right];
while(sum >= target)
{
len = min(len, right - left + 1);
sum -= nums[left++];
}
}
return len == INT_MAX ? 0 : len;
}
};
结果分析 :
滑动窗口的正确性:
这个窗口寻找的是: 以当前窗口最左侧元素(记为left1)为基准,符合条件的情况.也就是在这道题中,从left1开始,满足区间和sum >= target时的最右侧(记为right1)能到哪里.
我们既然已经找到从left1开始的最优的区间,那么就就可以大胆舍去left1.但是如果继续像方法一(暴力枚举)一样,重新开始统计第二个元素(left2)往后的和,势必会有大量重复的计算(因为我们在求第一段区间的时候),已经算出很多元素的和了,这些和可以在计算下次区间和的时候用上的).(就像一个滑动的区间记录这我们已经求过的值)
此时,right1的作用就体现出来了,我们只需将left1这个值从sum中剔除.从right1这个元素开始,往后找满足left2元素的区间(此时right也有可能满足的.因为left1可能很小.sum剔除掉left1
之后,依旧满足大于等于target).这样我们就能省掉大量重复的计算.
这样我们不仅能解决问题,而且就能省掉大量重复的计算
时间复杂度分析:
虽然代码时是两层循环,但是我们的left指针和right指针都是不会退的,两者最多都往后移动n次.因此时间复杂度是O(N).
4. 总结 :
滑动窗口这这道题中就是利用单调性,使用"同向双指针"来优化我们的暴力算法,就像一个滑动的窗口一样,记录这我们计算过的数.