当前位置: 首页 > news >正文

leetcode209------长度最小的子数组、滑动窗口

一、题目简介

题目链接:209. 长度最小的子数组 - 力扣(LeetCode)

给定一个含有 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 <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

二、解法

2.1 解法1:暴力解法

两层for循环遍历所有可能。 

// 长度最小的子数组
// [1,1,1,3,3] target=7,返回:3
#include<iostream>
#include<vector>
class solution{
    public:
    // 两层for循环,暴力解法
    int minSubArrayLen(int target, std::vector<int>& nums){   
        int sum=0; // 子序列的数值之和  
        int result = INT32_MAX;
        for(int i=0;i<nums.size();i++){ // 设置子序列的起点为i
            sum=0; // 这块的sum=0必须存在,否则程序运行结果不正确
            for(int j=i;j<nums.size();j++){ // 设置子序列终止位置为j
                sum=sum+nums[j]; 
                if(sum>=target){ // 一旦发现子序列和超过了target,更新result
                    int subLength = j-i+1; // 取子序列的长度 
                    result = result < subLength ? result : subLength; // 判断上一次的result是否小于此次subLength,若小于,则保留上一次的result;如果大于,则把此次的subLength的值赋给result,说明本轮找到的子序列长度要小于上一轮找到的子序列长度
                    break;
                }
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};


int main(){
    solution s1; // 对象实例化
    int target = 7; // 定义目标值
    std::vector<int> nums{1,2,3,4,5,6};
    int SubArraylen = s1.minSubArrayLen(target,nums);
    std::cout<<"子数组长度为:"<<SubArraylen<<std::endl;
    return 0;
}


给定数组[1,2,3,4,5,6],目标元素为target=7。

程序开始运行,进入函数内,执行流程为:

i=0,j=0,sum=0-->sum=1<target=7,不进入if判断语句。
i=0,j=1,sum=1-->sum=3<target=7,不进入if判断语句。
i=0,j=2,sum=3-->sum=6<target=7,不进入if判断语句。
i=0,j=3,sum=6-->sum=10>target=7,进入if判断语句,subLength=j-i+1=3-0+1=4,result=subLength=4。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。

接下来执行流程为:
i=1,j=1,sum=0-->sum=2<target=7,不进入if判断语句。
i=1,j=2,sum=2-->sum=5<target=7,不进入if判断语句。
i=1,j=3,sum=5-->sum=9>target=7,进入if判断语句,subLength=j-i+1=3-1+1=3,result=subLength=3。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。

接下来执行流程为:
i=2,j=2,sum=0-->sum=3<target=7,不进入if判断语句。
i=2,j=3,sum=3-->sum=7=target=7,进入if判断语句,subLength=j-i+1=3-2+1=2,result=subLength=2。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。

。。。。。

依此类推,直至遍历完所有的可能。

该方法的时间复杂度与空间复杂度如下: 

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

2.2 解法2:滑动窗口法

接下来就开始介绍数组操作中另一个重要的方法:滑动窗口

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

在暴力解法中,是一个for循环为滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。首先要思考如果用一个for循环,那么应该表示滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。所以 只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。那么问题来了,滑动窗口的起始位置如何移动呢?这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:

最后找到 4,3 是最短距离。

其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是满足其和 ≥ s 的长度最小的连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于窗口的起始位置如何移动,如图所示:

 

可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。 

// 长度最小的子数组
// [1,1,1,3,3] target=7,返回:3
#include<iostream>
#include<vector>
class solution{
    public:

    // 滑动窗口法【双指针法】
    int minSubArrayLen_slideWindow(int target, std::vector<int>& nums){
        int sum = 0; // 滑动窗口(子序列)数值之和
        int subLength = 0; // 定义滑动窗口的长度
        int i=0; // 定义滑动窗口起始位置
        int result = INT32_MAX;
        for(int j=0;j<nums.size();j++){
            sum = sum+nums[j];

            while(sum>=target){ // 一旦发现子序列和超过了target,更新result
                subLength = j-i+1;
                result = result<subLength? result:subLength;
                sum = sum-nums[i]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
                i++;
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0:result;
    } 

};


int main(){
    solution s1; // 对象实例化
    int target = 7; // 定义目标值
    std::vector<int> nums{1,2,3,4,5,6};
    int SubArraylen = s1.minSubArrayLen_slideWindow(target,nums);
    std::cout<<"子数组长度为:"<<SubArraylen<<std::endl;
    return 0;
}


给定数组[1,2,3,4,5,6],目标元素为target=7。

程序开始运行,进入函数内,执行流程为:

i=0,j=0,sum=0-->sum=1<target=7,不进入while循环。
i=0,j=1,sum=1-->sum=3<target=7,不进入while循环。
i=0,j=2,sum=3-->sum=6<target=7,不进入while循环。
i=0,j=3,sum=6-->sum=10>target=7,进入while循环。
进入while循环,
subLength=j-i+1=3-0+1=4,result=subLength=4,sum=sum-nums[i],则有:sum=10-->sum=9,随后执行i++语句后,i变为1,j仍然为3。
sum=9依然大于target,再次进入while循环,subLength=j-i+1=3-1+1=3,result=subLength=3,sum=sum-nums[i],则有:sum=9-->sum=7,随后执行i++语句后,i变为2,j还是3。
sum=7等于target,再次进入while循环,subLength=j-i+1=3-2+1=2,result=subLength=2,sum=sum-nums[i],则有:sum=7-->sum=4,随后执行i++语句后,i变为3。
此时sum为4,不符合while循环条件,退出while循环。
经过while循环之后,新的滑动窗口起始位置已经被找出,即i=3。接下来便可进行下一次for循环以更新j的值,随后按照上述流程执行即可。

该方法的时间复杂度和空间复杂度如下:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

一些录友会疑惑为什么时间复杂度是O(n)

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

推荐算法解读视频:

拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili

相关文章:

  • TCP/IP 5层协议簇:数据链路层(交换机工作原理)
  • nacos升级2.3.0-2.4.1
  • 【MySql】EXPLAIN执行计划全解析:15个字段深度解读与调优指南
  • 【深度学习】PyTorch v2.6 Overview
  • 设计后端返回给前端的返回体
  • 钉钉MAKE AI生态大会思考
  • C++ | 高级教程 | 泛型模板
  • 数据结构秘籍(一)线性数据结构
  • 注意力机制有哪些,原理是什么
  • 细说STM32F407单片机RS485收发通信实例及调试方法
  • wordpress使用CorePress主题设置项总结
  • Elasticsearch:使用经过训练的 ML 模型理解稀疏向量嵌入
  • Python Pandas带多组参数和标签的Oracle数据库批量数据导出程序
  • MySQL-MATCH ... AGAINST工具
  • linux--多进程开发(5)--进程间通信(IPC)、linux间通信的方式、管道
  • 全价值链数字化转型:以美的集团为例,探索开源AI大模型与S2B2C商城小程序源码的融合应用
  • MySQL之Redo Log:确保数据持久性和崩溃恢复
  • MySql 获取表结构的4中方法
  • 【如何优化 Vue.js 项目的性能?】
  • mfy学习笔记
  • 做电商网站外包/网络营销好不好
  • 微商城网站建设市场/牛奶推广软文文章
  • 宝安网站建设公司/医院网站建设方案
  • 陕西网络公司/搜索引擎优化的名词解释
  • 购物网站的功能板块/seo免费培训视频
  • 凡科的模板做网站/大数据精准营销获客