力扣日记8.1-【数组篇】长度最小的子数组

力扣日记:【数组篇】长度最小的子数组

日期:2023.8.1
参考:代码随想录

209.长度最小的子数组

题目描述

给定一个含有 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] <= 105

进阶:

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

题解

class Solution {
#define SOLUTION 3
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        // 暴力双层for循环(超出时间限制了。。。)
#if SOLUTION == 1
        int len = nums.size();
        int minLen = len + 1;
        for (int i = 0; i < len; i++) {
            int sum = 0;
            for (int j = i; j < len; j++) {
                sum += nums[j];
                if (j - i + 1 >= minLen) {
                    break;
                } else if (sum >= target) {
                    minLen = j - i + 1;
                    break;
                }
            }
        }
        if (minLen <= len) {
            return minLen;
        } else {
            return 0;
        }
#elif SOLUTION == 2
        // 滑动窗口
        int len = nums.size();
        // 滑动窗口起始位置为移动指针 start (慢指针)
        // 滑动窗口终止位置为 for循环 end (快指针)
        int start = 0;
        int sum = 0;
        int minLen = len + 1;
        for(int end = 0; end < len; end++) {
            sum += nums[end];
            if (sum >= target) {
                while (start <= end) {
                    if (sum - nums[start] >= target) {
                        // start++;
                        sum -= nums[start++];
                    } else {
                        break;
                    }
                }
                
                if (end - start + 1 < minLen)
                    minLen = end - start + 1;
            }
        }
        if (minLen <= len)
            return minLen;
        else
            return 0;
#else
        // 滑动窗口(代码随想录)
        
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= target) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
#endif
    }
};

执行结果

状态:通过
执行用时: 24 ms(95%)
内存消耗: 27.4 MB (94%)
时间复杂度:O(n)
空间复杂度:O(1)

思路总结

  • 一如既往的做不出来www 只想到了暴力解法,结果超出时间限制了。参考代码随想录的思路,最后自己写了一遍滑动窗口解法。
  • 关于滑动窗口:
    • 滑动窗口即“通过不断的调节子序列的起始位置和终止位置,从而得出想要的结果。” 其实也是一种双指针,其中滑动窗口起始位置为慢指针 start,终止位置为快指针end
    • 怎么想到用滑动窗口:暴力解法的思路较容易想到,但是由于要用到双层for循环,时间复杂度为O(n^2)。为了使复杂度将为O(n),想到将一层for循环用慢指针代替。
    • 至于为什么起始位置用慢指针,终止位置用快指针,代码随想录的解释是:“如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。”
    • 滑动过程(我的思路,对应第二种解法):首先移动快指针(终止位置)直到满足条件(窗口中的值的和 ≥ target),若满足条件,试着移动起始位置看是否能缩小窗口,能(即移动起始位置后仍满足条件)则将起始位置往后移,同时和sum减去移出窗口外的值。当窗口不能继续缩小了,则继续移动终止位置,以此类推。
    • 代码随想录版本更为简洁(见下方)。
      力扣日记8.1-【数组篇】长度最小的子数组_第1张图片
    • 为什么时间复杂度是O(n):(还未理解。。。)
      在这里插入图片描述

你可能感兴趣的:(leetcode,算法,职场和发展)