滑动窗口(1)_长度最小的子数组

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

滑动窗口(1)_长度最小的子数组

收录于专栏【经典算法练习
本专栏旨在分享学习C++的一点学习笔记,欢迎大家在评论区交流讨论💌

 

目录

1. 题目链接:

2. 题目描述 :

3. 解法 :

    解法一(暴力枚举) :

    算法思路 :

    具体步骤:

    代码展示 :

    结果分析 :

    解法二(滑动窗口) :

   滑动窗口简介

   算法思路 :

    图解流程 :

    代码展示 :

    结果分析 :

滑动窗口的正确性:

时间复杂度分析: 

4. 总结 :


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. 解法 :

    解法一(暴力枚举) :

    算法思路 :

[从前往后]枚举数组中的任意一个元素,把它当成起始位置.然后从这个[起始位置]开始,寻找一段最短的区间,使得这段区间的和[大于等于]目标值.

将所有元素作为起始位置所得的结果中,找到[最小值]即可.

 具体步骤:

  1. 用i, j两层循环遍历完整个数组
  2. 用sum统计[i, j]区间之和
  3. 如果sum >= target, 说明j没有移动的必要(数组内全是正整数)就让i移动
  4. 移动前更新一下最小长度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. 总结 :

滑动窗口这这道题中就是利用单调性,使用"同向双指针"来优化我们的暴力算法,就像一个滑动的窗口一样,记录这我们计算过的数.

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值