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

难度参考

        难度:简单

        分类:数组

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

题目

        给定一个含有个正整数的数组和一个正整数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张图片

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

滑动窗口

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

  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的长度),第一次更新滑动窗口长度。

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

        尝试缩小窗口(移动左指针),发现3+1+2=6<7。

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

        因此,继续寻找(移动右指针),调整窗口(1+2+4>7),第二次更新滑动窗口长度。

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

        同理,在尝试缩小窗口(移动左指针【先】)与继续寻找(移动右指针【后】)之后,调整窗口(1+2+4>7),第三次更新滑动窗口长度。

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

        尝试缩小窗口(移动左指针),发现4+3>=7,第四次更新滑动窗口长度。

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

        尝试缩小窗口(移动左指针),发现3<7, 继续寻找(移动右指针), 右指针 j > 数组长度,结束循环。我们就得到了所需要的窗口长度(即该数组中满足其和≥s的长度最小的连续子数组的长度)。

        结果:

        在整个过程中,我们不断调整窗口的大小,以找到和大于等于7的最小子数组。最终,我们找到了一个和为7的子数组,长度为2。这是我们要找的答案。

        所以,滑动窗口算法的结果是2,表示最小连续子数组的长度为2,即子数组 [4, 3]

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

梳理

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

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

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

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

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

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

        总结来说,滑动窗口算法通过动态地维护一个窗口,根据窗口内元素和的大小来调整窗口的位置和大小,从而找到满足条件的最小子数组。它的核心思想是不断地搜索可能的子数组,然后选择最小长度的子数组作为答案。这个算法的时间复杂度为O(n),因为每个元素最多被访问两次(一次添加到窗口,一次从窗口移除),其中n是数组的长度。

代码

暴力做法

#include 
#include 
#include  // 包含  头文件以引入 INT_MAX

using namespace std;

// 定义一个函数,找到满足和≥s的最短连续子数组的长度(暴力法)
int minSubArrayLen(int s, vector& 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 nums = {2, 3, 1, 2, 4, 3};  // 给定的正整数数组

    // 调用函数找到满足条件的最短连续子数组的长度(暴力法)
    int result = minSubArrayLen(s, nums);

    cout << "最小连续子数组的长度为:" << result << endl;  // 输出结果

    return 0;
}

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

滑动窗口

#include 
#include 
#include  // 包含  头文件以引入 INT_MAX

using namespace std;

// 定义一个函数,找到满足和≥s的最短连续子数组的长度
int minSubArrayLen(int s, vector& 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 nums = {2, 3, 1, 2, 4, 3};  // 给定的正整数数组

    // 调用函数找到满足条件的最短连续子数组的长度
    int result = minSubArrayLen(s, nums);

    cout << "最小连续子数组的长度为:" << result << endl;  // 输出结果

    return 0;
}

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

打卡

        暴力做法打卡

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

        滑动窗口打卡

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

你可能感兴趣的:(算法编程笔记,流程图)