开启掘金成长之旅!这是我参与「掘金日新计划 · 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上还有很多相关的题目,大家可以多多去巩固。