C/C++滑动窗口算法深度解析与实战指南
引言
滑动窗口算法是解决数组/字符串连续子序列问题的利器,通过动态调整窗口边界,将暴力解法的O(n²)时间复杂度优化至O(n)。本文将系统讲解滑动窗口的核心原理、C/C++实现技巧及经典应用场景,助您掌握这一高效算法。
一、算法核心原理
1.1 窗口双指针模型
- 左右指针:使用
left
和right
指针界定窗口边界 - 动态调整:
- 扩展窗口:右指针右移扩大窗口范围
- 收缩窗口:当条件不满足时左指针右移缩小窗口
- 状态维护:通过哈希表/数组记录窗口内元素状态
1.2 两种窗口类型
窗口类型 | 特点 | 典型场景 |
---|---|---|
固定窗口 | 窗口大小恒定 | 滑动平均值、固定长度子数组 |
可变窗口 | 窗口大小动态调整 | 最长无重复子串、最小覆盖子串 |
二、C/C++实现详解
2.1 固定窗口实现模板
int fixedWindow(vector<int>& nums, int k) {int sum = 0, max_sum = 0;// 初始化窗口for (int i = 0; i < k; ++i) sum += nums[i];max_sum = sum;// 滑动窗口for (int right = k; right < nums.size(); ++right) {sum += nums[right] - nums[right - k]; // 滚动更新max_sum = max(max_sum, sum);}return max_sum;
}
2.2 可变窗口实现模板
int variableWindow(string s) {unordered_map<char, int> window;int left = 0, max_len = 0;for (int right = 0; right < s.size(); ++right) {char c = s[right];window[c]++; // 扩展窗口// 收缩条件:出现重复字符while (window[c] > 1) {char d = s[left];window[d]--; // 移出左边界left++;}max_len = max(max_len, right - left + 1); // 更新结果}return max_len;
}
三、经典问题解析
3.1 无重复字符的最长子串(LeetCode 3)
int lengthOfLongestSubstring(string s) {vector<int> char_map(128, -1); // ASCII映射表int max_len = 0, left = 0;for (int right = 0; right < s.size(); ++right) {if (char_map[s[right]] >= left) {left = char_map[s[right]] + 1; // 跳跃收缩}char_map[s[right]] = right; // 更新最新位置max_len = max(max_len, right - left + 1);}return max_len;
}
3.2 最小覆盖子串(LeetCode 76)
string minWindow(string s, string t) {unordered_map<char, int> need, window;for (char c : t) need[c]++;int left = 0, valid = 0, start = 0, min_len = INT_MAX;for (int right = 0; right < s.size(); ++right) {char c = s[right];if (need.count(c)) {window[c]++;if (window[c] == need[c]) valid++;}// 收缩窗口while (valid == need.size()) {if (right - left + 1 < min_len) {min_len = right - left + 1;start = left;}char d = s[left++];if (need.count(d)) {if (window[d] == need[d]) valid--;window[d]--;}}}return min_len == INT_MAX ? "" : s.substr(start, min_len);
}
四、性能优化技巧
4.1 空间优化
- 数组替代哈希表:当字符集确定时(如ASCII),使用数组存储频次
int char_count[128] = {0}; // 替代unordered_map
4.2 时间优化
- 跳跃收缩:发现重复元素时直接跳转到重复位置+1
- 提前终止:当窗口长度已达理论最大值时break
五、复杂度分析
场景 | 时间复杂度 | 空间复杂度 |
---|---|---|
固定窗口 | O(n) | O(1) |
可变窗口(哈希表) | O(n) | O(Σ) |
可变窗口(数组) | O(n) | O(1) |
六、应用场景拓展
-
字符串处理:
- 字母异位词检测
- DNA序列分析
- 回文子串查找
-
数组问题:
- 最大连续1的个数
- 乘积小于K的子数组
- 股票买卖时机分析
-
数据流处理:
- 实时移动平均值计算
- 异常值检测
七、常见错误避坑指南
- 指针越界:确保
left <= right
且right < n
- 状态残留:窗口收缩后需及时更新状态变量
- 循环条件:可变窗口必须使用
while
收缩而非if
- 初始值设置:max_len应初始化为0而非INT_MIN
结语
滑动窗口算法通过精妙的指针操作,将复杂度从平方级别降至线性,是解决连续子序列问题的首选方案。掌握其核心思想与实现技巧,您将能高效解决LeetCode 3、76、209等经典题目。建议通过大量练习加深理解,特别是对窗口收缩条件的判断和状态维护的细节处理。