算法练习-长度最小的子数组(思路+流程图+代码)

news/2024/5/18 21:30:20 标签: 流程图

难度参考

        难度:简单

        分类:数组

        难度与分类由我所参与的培训课程提供,但需要注意的是,难度与分类仅供参考。以下内容均为个人笔记,旨在督促自己认真学习。

题目

        给定一个含有个正整数的数组和一个正整数s,找出该数组中满足其和≥s的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0。

        示例1:

        输入:s=7,
                   nums=[2,3,1,2,4,3]
        输出:2
        解释:子数组[4,3]是该条件下的长度最小的子数组。

思路

暴力做法

        使用暴力法解决这道题的思路是遍历所有可能的连续子数组,计算它们的和,并找到满足条件的最小子数组长度。以下是暴力法的详细题解:

  1. 初始化一些变量,包括最小长度 minLength 初始为正无穷大。

  2. 使用两层循环,外层循环以每个元素为起点,内层循环遍历从该起点开始的子数组。外层循环变量 start 从0开始,内层循环变量 endstart 开始。

  3. 在内层循环中,计算子数组的和 sum,即从 nums[start]nums[end] 的元素的累加和。

  4. 如果 sum 大于或等于目标值 s,说明当前子数组的和满足条件,可以记录下当前子数组的长度 end - start + 1

  5. 在外层循环中,不断更新 minLength,即记录当前满足条件的子数组的最小长度。

  6. 继续外层循环,直到遍历完整个数组。

  7. 最后,如果 minLength 没有被更新过,说明没有满足条件的子数组,返回0;否则,返回 minLength

        这个算法的核心思想是遍历所有可能的子数组,计算它们的和并比较长度,找到最小长度的满足条件的子数组。由于使用了两层循环,时间复杂度是O(n^2),其中n是数组的长度。这个算法虽然不如滑动窗口法高效,但是可以解决问题。

        暴力做法不再提供示例与梳理,感觉可以直接看代码。

滑动窗口

        可以使用滑动窗口的方法解决这个问题。滑动窗口是维护一个连续子数组的常用技巧,通过左指针和右指针来移动窗口,根据窗口内元素的和来调整窗口的大小。具体步骤如下:

  1. 初始化左指针 left 为0,右指针 right 为0,以及窗口内元素的和 sum 为0。

  2. 使用右指针 right 向右遍历数组,不断将元素添加到窗口内,并更新 sum

  3. sum 大于等于给定的正整数 s 时,记录当前窗口的长度 right - left + 1

  4. 缩小窗口,即左指针 left 向右移动,同时从 sum 中减去左边界的元素,直到 sum 小于 s

  5. 重复步骤2到4,直到遍历完整个数组。

  6. 在整个过程中,不断更新最小子数组的长度,最终得到最小长度。

通过滑动窗口找到最小长度的连续子数组,时间复杂度为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]

梳理

        滑动窗口算法之所以能够实现找到满足条件的最小连续子数组,是因为它巧妙地利用了窗口的概念,通过不断调整窗口的大小和位置,来搜索满足条件的最小子数组。以下是为什么这个算法能够实现的原因:

  1. 窗口的左右边界移动: 算法使用两个指针,一个左指针和一个右指针,它们分别表示当前窗口的左边界和右边界。通过不断移动这两个指针,算法模拟了不同窗口的情况。

  2. 窗口内元素和的计算: 算法维护一个变量 sum,用于记录当前窗口内元素的和。随着右指针的移动,不断将新元素添加到窗口内,并更新 sum。这使得算法能够动态地计算窗口内元素的和。

  3. 根据和的大小调整窗口: 在每一步中,算法检查 sum 是否满足给定的条件(例如,是否大于等于s)。如果满足条件,算法会记录当前窗口的长度,然后尝试缩小窗口,即移动左指针。如果不满足条件,算法会继续扩大窗口,即移动右指针。

  4. 不断更新最小长度: 算法在整个过程中不断记录最小的子数组长度。每当找到一个满足条件的子数组时,它会与之前记录的最小长度比较,然后更新最小长度。这确保了算法找到的是最小的满足条件的子数组。

  5. 遍历整个数组: 算法通过不断移动右指针,遍历整个数组,以寻找满足条件的子数组。因为算法考虑了数组中的每个元素,所以它能够找到所有可能的子数组,从中选择最小长度的子数组。

        总结来说,滑动窗口算法通过动态地维护一个窗口,根据窗口内元素和的大小来调整窗口的位置和大小,从而找到满足条件的最小子数组。它的核心思想是不断地搜索可能的子数组,然后选择最小长度的子数组作为答案。这个算法的时间复杂度为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)

打卡

        暴力做法打卡

        滑动窗口打卡


http://www.niftyadmin.cn/n/5332683.html

相关文章

路由器的妙用:使用无线路由器无线桥接模式充当电脑的无线网卡

文章目录 需求说明第一步&#xff1a;重置、连接路由器第二步&#xff1a;设置无线桥接模式第三步&#xff1a;电脑连接路由器上网 需求说明 在原路由无线覆盖的范围内&#xff0c;使用无网卡台式和其他主机&#xff0c;并且有闲置的无线路由器或者网线太短&#xff0c;可以使…

机器学习之伯努利分布及二项分布

伯努利分布:又称两点分布或0-1分布,其样本空间只有两个点,一般取{0,1},不同的伯努利分布只是取到这两个值的概率不一样。伯努利分布只有一个参数p(用描述取1的概率),记作 B e r n o u l l ( p ) Bernoull(p) Bernoull(p)或 X X X~ B ( p ) B(p) B(p)读作X服从参数为p的…

文件拖拽丢失了怎么办?5个实用方法教你找回!

“我有一个电脑磁盘太满了&#xff0c;于是我想将部分文件移动到另一个磁盘上&#xff0c;但是不知道为什么在拖拽的过程中&#xff0c;部分文件莫名其妙就丢失了&#xff0c;怎么找回这些丢失的文件呢&#xff1f;” 在日常使用电脑的过程中&#xff0c;我们经常会通过拖拽的方…

VSCode Live Server插件 开启了自动刷新,依旧不刷新解决方法

困扰了很久的live sever不能自动刷新的,找了很多教程都没成功,后面终于成功了!!!开心啊 问题:live serve插件打开页面后不自动刷新页面,需要自己点刷新 解决方法: 1.首先设置vscode为自动保存 2、打开VS code &#xff0c;重新安装live sever&#xff0c;重新启动测试&#xf…

VCG 基于连通性网格面片聚类

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里的思路其实与点云的欧式聚类非常类似,区别在于点云的欧式聚类是通过搜索半径对点云进行聚类,至于基于连通性网格面片聚类则是通过面片的邻近关系对面片进行聚类,大致的过程与欧式聚类是相同的: 首先,需要指…

面试题-操作系统中进程的状态有哪些

进程的状态 在操作系统中&#xff0c;进程是一个执行中程序的实例。操作系统为了管理多个进程&#xff0c;通常会为每个进程维护一个状态。一个进程在其生命周期中可能会有以下几种基本状态&#xff1a; 新建状态 (New)&#xff1a; 当进程刚刚创建时&#xff0c;它处于新建状…

redis数据安全(五)事务

一、概念&#xff1a; 1、介绍&#xff1a;Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令&#xff0c;一个事务中所有命令都会被序列化。在事务执行过程&#xff0c;会按照顺序串行化执行队列中的命令&#xff0c;其他客户端提交的命令请求不会插入到事务执行命…

Producer源码解读

Producer源码解读 在 Kafka 中, 我们把产生消息的一方称为 Producer 即 生产者, 它是 Kafka 的核心组件之一, 也是消息的来源所在。它的主要功能是将客户端的请求打包封装发送到 kafka 集群的某个 Topic 的某个分区上。那么这些生产者产生的消息是怎么传到 Kafka 服务端的呢&a…