文章目录
在算法设计中,滑动窗口是一种高效的技巧,尤其在处理连续子数组或子串问题时非常有用。滑动窗口的核心思想是使用两个指针,定义一个范围,在这个范围内计算所需的值,然后根据问题的要求移动窗口的边界。本文将详细剖析几道经典题目,结合代码来讲解滑动窗口的原理和应用。
什么是滑动窗口?
滑动窗口技术通常用于解决子数组或子串相关的问题。其主要思想是在数组或字符串上维持一个固定的窗口大小,或在特定条件下调整窗口大小,从而在窗口内进行高效的计算。滑动窗口技术可以帮助我们在O(n)的时间复杂度内解决一些需要遍历整个数组或字符串的问题。
滑动窗口的基本步骤包括:
- 初始化窗口的左右边界(通常为两个指针)。
- 移动窗口的右边界扩展窗口范围,直至满足某些条件。
- 移动窗口的左边界收缩窗口,直至不再满足条件。
- 记录或更新需要的结果。
接下来,我们通过几道经典的滑动窗口问题,来深入理解这一技巧的应用。
经典题型分析与讲解
1. 长度最小的子数组
题目描述:
给定一个含有n个正整数的数组和一个正整数**target**
,找出该数组中满足其和大于等于**target**
的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0。
滑动窗口思路:
- 我们使用两个指针
**left**
和**right**
表示窗口的左右边界。 - 初始时两个指针都指向数组的起点。
- 扩展
**right**
指针,使窗口内的数字和逐渐增大。 - 当窗口内的和大于等于
**target**
时,收缩**left**
指针以找到最小的子数组长度。 - 在整个过程中,动态更新最小长度。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int right = 0;
int len = INT_MAX; // 初始化最小子数组长度为无穷大
int n = nums.size();
int sum = 0; // 当前窗口的数字和
// 遍历数组,扩展右边界
for (; right < n; right++) {
sum += nums[right]; // 将当前右边界的数字加入窗口的和中
// 当窗口内的和大于或等于目标值时,缩小窗口
while (sum >= target) {
len = min(len, right - left + 1); // 更新最小子数组长度
sum -= nums[left]; // 减去左边界的数字,将窗口左边界右移
left++;
}
}
// 如果找不到符合条件的子数组,返回0;否则返回最小长度
return len == INT_MAX ? 0 : len;
}
};
复杂度分析:
- 时间复杂度:O(n),其中n为数组的长度。每个元素在扩展和收缩窗口的过程中最多只会被访问两次。
- 空间复杂度:O(1),仅使用了常数个额外空间。
2. 无重复字符的最长子串
题目描述:
给定一个字符串**s**
,请你找出其中不含有重复字符的最长子串的长度。
滑动窗口思路:
- 使用一个哈希表
**hash**
来记录窗口内字符的频率。 - 移动
**right**
指针扩展窗口,加入字符到哈希表中。 - 如果窗口内出现重复字符,则移动
**left**
指针收缩窗口,直到不再有重复字符。 - 在整个过程中,动态更新最大子串长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[200] = {
0 }; // 用于记录字符的频率
int left = 0;
int right = 0;
int ret = 0;
while (right < s.size()) {
hash[s[right]]++;
// 当出现重复字符时,收缩窗口
while (hash[s[right]] > 1) {
hash[s[left++]]--;
}
// 更新最长子串长度
ret = max(ret, right - left + 1);
right++;
}
return ret;
}
};
复杂度分析:
- 时间复杂度:O(n),字符串的每个字符最多访问两次。
- 空间复杂度:O(1),哈希表的大小为固定的常数级。
3. 最长重复子数组
题目描述:
给定一个二进制数组**nums**
和一个整数**k**
,如果可以将最多**k**
个**0**
变成**1**
,求最长的连续**1**
的长度。
滑动窗口思路:
- 使用两个指针
**left**
和**right**
表示滑动窗口。 - 每次扩展
**right**
指针,将遇到的**0**
记录在计数器**counter**
中。 - 当窗口内
**0**
的个数大于**k**
时,收缩窗口,直到**0**
的个数不超过**k**
。 - 在整个过程中,动态更新最大连续
**1**
的长度。
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int left = 0;
int right