leetcode 面试经典 150 题:长度最小的子数组
链接 | 长度最小的子数组 |
---|---|
题序号 | 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] <= 104进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
题解
滑动窗口法
-
子数组:是数组中连续的、非空元素序列。
-
核心要点:
- 滑动窗口:使用滑动窗口(双指针)的方法来解决这个问题。定义两个指针 left 和 right,分别表示子数组的开始和结束位置。
- 窗口和:维护一个变量 sum,表示当前窗口内元素的和。
- 窗口移动:当 sum < s 时,移动 right 指针向右扩展窗口,并将 nums[right] 加入 sum。当 sum ≥ s 时,记录当前窗口的长度,并尝试移动 left 指针向右缩小窗口,同时从 sum 中减去 nums[left]。
- 最小长度:在每次满足 sum ≥ s 时,更新最小长度的记录。
-
时间复杂度:O(n),因为每个元素最多被访问两次(一次被 right 指针访问,一次被 left 指针访问)。
-
空间复杂度:O(1),因为只使用了常数级别的额外空间
-
c++ 实现算法:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int left = 0; // 滑动窗口左边界
int sum = 0; // 当前窗口的总和
int minLength = INT_MAX; // 最小子数组长度,初始化为无穷大
for (int right = 0; right < n; ++right) {
sum += nums[right]; // 将当前元素加入窗口
// 当窗口内的和满足 >= target 时,尝试缩小窗口
while (sum >= target) {
minLength = min(minLength, right - left + 1); // 更新最小长度
sum -= nums[left]; // 从窗口中移除左边界的值
++left; // 左边界右移
}
}
// 如果没有找到符合条件的子数组,返回 0
return minLength == INT_MAX ? 0 : minLength;
}
};
-
演示:以示例1为例
-
c++完整demo:
#include <iostream>
#include <vector>
#include <climits> // for INT_MAX
using namespace std;
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int left = 0; // 滑动窗口左边界
int sum = 0; // 当前窗口的总和
int minLength = INT_MAX; // 最小子数组长度,初始化为无穷大
for (int right = 0; right < n; ++right) {
sum += nums[right]; // 将当前元素加入窗口
// 当窗口内的和满足 >= target 时,尝试缩小窗口
while (sum >= target) {
minLength = min(minLength, right - left + 1); // 更新最小长度
sum -= nums[left]; // 从窗口中移除左边界的值
++left; // 左边界右移
}
}
// 如果没有找到符合条件的子数组,返回 0
return minLength == INT_MAX ? 0 : minLength;
}
};
int main() {
Solution solution;
vector<int> nums = {2, 3, 1, 2, 4, 3};
int target = 7;
int result = solution.minSubArrayLen(target, nums);
cout << "result: " << result << endl; // 输出 2
return 0;
}
滑动窗口法和双指针法
滑动窗口和双指针是解决数组和字符串问题中常用的两种技术,它们在某些情况下可以相互转换,但它们的概念和应用场景有所不同。下面我将分别解释这两种技术,并说明它们的主要区别。
滑动窗口
滑动窗口是一种处理 固定长度或动态长度窗口内元素的技术,常用于解决需要在连续子数组或子字符串中寻找特定属性的问题。 滑动窗口的核心思想是维护一个窗口,随着遍历的进行,窗口内包含的元素会不断变化。
特点:
- 窗口可以是固定长度,也可以是动态变化的。
- 窗口内的操作通常是累加、累乘或者比较。
- 滑动窗口可以用于寻找子数组或子字符串的最大/最小值、和、乘积等。
应用场景:
- 寻找子数组的和或乘积满足特定条件的最小/最大长度。
- 判断子数组是否包含特定数量的不同元素。
- 实现一些需要连续性条件的数据流统计。
双指针
双指针技术通常涉及两个指针(或索引),它们在数组或字符串中以不同的速度移动,用于解决需要比较元素之间相对位置的问题。
特点:
- 两个指针可以同时移动,也可以一个快一个慢。
- 双指针常用于比较、排序、去重、寻找特定模式等问题。
- 双指针可以用于解决有序数组中的特定问题,如寻找两个数的和、差等。
应用场景:
- 寻找两个指针之间的特定关系,如两数之和为特定值。
- 判断一个数组是否为回文(快慢指针相向而行)。
- 实现归并排序、快速排序中的合并和分区步骤。
区别
- 窗口大小:滑动窗口通常有一个明确的窗口大小,而双指针不一定有固定的大小概念。
- 移动方式:滑动窗口通常是单向移动(如向右扩展),而双指针可以相向而行或同向而行。
- 问题类型:滑动窗口更适合解决需要连续性的问题,而双指针更适合解决需要比较元素相对位置的问题。
- 灵活性:双指针在某些情况下比滑动窗口更灵活,因为它们可以独立控制每个指针的移动。
在实际应用中,滑动窗口和双指针可以根据问题的具体需求相互转换。例如,滑动窗口问题可以通过设置两个指针来模拟,而某些双指针问题也可以通过扩展窗口来解决。理解这两种技术的核心思想和适用场景,可以帮助你更有效地解决算法问题。