一题带你探索【滑动窗口】的奥秘

104 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第31天,点击查看活动详情

最近挺久没刷算法题了,今天心血来潮刷了一道 LeetCode 题目。觉得解题的过程还蛮有意思,整理成文字,分享出来给大家一起学习。

题目

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

leetcode 链接 leetcode.cn/problems/mi…

示例 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

暴力求解

相信大家第一个想到的方案,一定是暴力求解法。

首先第一个 for 循环,表示当前数组的所遍历的元素 i ;第二个 for 循环,以该元素 i 为起点,往数组后遍历求和,直到和大于等于 target 值;获取到对应的下标。这样就能够求出其中一个连续子数组的长度了。然后,我们对每一个连续子数组的长度取最小的一个,就能够得到我们需要的答案了。

下面我们看看代码的实现:

public static int minSubArrayLen(int target, int[] nums) {
    int sum = 0;
    int subLength = 0; 
    int result = Integer.MAX_VALUE;
    for (int i = 0; i < nums.length; i++) {
        sum = 0;
        for (int j = i; j < nums.length; j++) {
            sum += nums[j];
            if (sum >= target) {
                subLength = j - i + 1;
                result = Math.min(result, subLength);
                break;
            }
        }
    }
    return result == Integer.MAX_VALUE ? 0 : result;
}

很明显,我们这里有两个 for 循环,里面的 for 循环需要遍历 n 个元素,外面的 for 循环也需要遍历 n 个元素,所以

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

滑动窗口

滑动窗口是暴力求解法的一种优化,同样也是一种双指针的变体

滑动窗口其实就是不断的调节子序列的起始位置和终止位置,从而得出符合预期的结果

滑动窗口解题的精髓在于以下三点:

  • 窗口内的元素
  • 窗口的起始位置如何确定和改变?
  • 窗口的结束位置如何确定和改变?

在我们这一题里,要在滑动窗口中的元素就是能够满足其和 ≥ target 的长度最小的 连续 子数组。

窗口的起始位置:首先要从数组的第一个元素开始找,也就是说起始的点肯定是0。如果当前窗口的值大于 target 了,窗口就需要向前移动了(即窗口需要缩小了)。

窗口的结束位置:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

public static int minSubArrayLen(int target, int[] nums) {
    int result = Integer.MAX_VALUE; // 最终的结果
    int sum = 0; // 子序列的数值之和
    int subLength = 0; // 子序列的长度
    int left = 0; // 滑动窗口起点
    for (int right = 0; right < nums.length; right++) { // 滑动窗口终点
        sum += nums[right];
        while (sum >= target) {
            subLength = (right - left + 1); // 取子序列的长度
            result = Math.min(result, subLength);
            sum -= nums[left++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
        }
    }
    return result == Integer.MAX_VALUE ? 0 : result;
}

这里的优化其实是里面的 for 循环遍历的元素并不多,也就达到了优化的效果。相信大家看过注释之后,就能够理解滑动窗口这里面的逻辑了。再看看简单复杂度:

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

好了,今天滑动窗口的解题案例就写到这里,LeetCode上还有很多相关的题目,大家可以多多去巩固。