滑动窗口算法
滑动窗口算法是一种常用的数组/字符串处理技巧,它通过维护一个窗口(通常是连续的子数组或子串)来高效解决一系列问题。这种算法能够将某些暴力解法的时间复杂度从O(n²)降低到O(n),在处理子串、子数组问题时特别有效。
滑动窗口的两种基本类型
1. 固定大小窗口
窗口大小在算法运行过程中保持不变,主要用于解决定长子数组/子串的相关问题。
示例场景:
-
长度为k的连续子数组的最大和
-
大小为k的滑动窗口的最大值
2. 可变大小窗口
窗口大小根据特定条件动态调整,通常用于解决满足某些条件的最短/最长子串问题。
示例场景:
-
无重复字符的最长子串
-
最小覆盖子串
-
长度最小的子数组
滑动窗口算法框架
// 可变大小窗口通用模板
int slidingWindow(char* s) {
int left = 0, right = 0; // 窗口左右边界
int maxLen = 0; // 存储最终结果
int window[256] = {0}; // 用于记录窗口内字符出现次数
while (right < strlen(s)) {
// 扩大窗口
char c = s[right];
window[c]++;
right++;
// 判断是否需要收缩窗口
while (/* 窗口需要收缩的条件 */) {
// 缩小窗口
char d = s[left];
window[d]--;
left++;
}
// 更新结果
maxLen = fmax(maxLen, right - left);
}
return maxLen;
}
经典例题解析
例题1:无重复字符的最长子串
问题描述: 给定一个字符串,找出其中不含有重复字符的最长子串的长度。
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int lengthOfLongestSubstring(char* s) {
int left = 0, right = 0;
int maxLen = 0;
int window[256] = {0}; // ASCII字符集
while (right < strlen(s)) {
char c = s[right];
window[c]++;
right++;
// 当窗口中出现重复字符时,收缩窗口
while (window[c] > 1) {
char d = s[left];
window[d]--;
left++;
}
// 更新最长子串长度
maxLen = fmax(maxLen, right - left);
}
return maxLen;
}
// 测试用例
int main() {
char s1[] = "abcabcbb";
char s2[] = "bbbbb";
char s3[] = "pwwkew";
printf("'%s': %d\n", s1, lengthOfLongestSubstring(s1)); // 输出: 3
printf("'%s': %d\n", s2, lengthOfLongestSubstring(s2)); // 输出: 1
printf("'%s': %d\n", s3, lengthOfLongestSubstring(s3)); // 输出: 3
return 0;
}
例题2:长度最小的子数组
问题描述: 给定一个含有n个正整数的数组和一个正整数target,找出该数组中满足其和≥target的长度最小的连续子数组。
#include <stdio.h>
#include <limits.h>
int minSubArrayLen(int target, int* nums, int numsSize) {
int left = 0, right = 0;
int sum = 0;
int minLen = INT_MAX;
while (right < numsSize) {
// 扩大窗口
sum += nums[right];
right++;
// 当窗口和满足条件时,尝试收缩窗口
while (sum >= target) {
// 更新最小长度
minLen = (minLen < right - left) ? minLen : (right - left);
// 收缩窗口
sum -= nums[left];
left++;
}
}
return minLen == INT_MAX ? 0 : minLen;
}
// 测试用例
int main() {
int nums1[] = {2, 3, 1, 2, 4, 3};
int target1 = 7;
int nums2[] = {1, 4, 4};
int target2 = 4;
printf("示例1: %d\n", minSubArrayLen(target1, nums1, 6)); // 输出: 2
printf("示例2: %d\n", minSubArrayLen(target2, nums2, 3)); // 输出: 1
return 0;
}
常见问题与调试技巧
1. 边界条件处理
问题: 窗口越界或结果更新时机错误 解决方案:
-
仔细检查循环终止条件
-
在关键位置添加打印语句调试
// 调试示例:添加详细日志
void debugSlidingWindow(char* s) {
int left = 0, right = 0;
int window[256] = {0};
while (right < strlen(s)) {
printf("窗口范围: [%d, %d), 子串: ", left, right);
for (int i = left; i < right; i++) {
printf("%c", s[i]);
}
printf("\n");
// ... 其余代码
}
}
2. 结果更新时机
关键点:
-
固定窗口:每次移动后都可以更新结果
-
可变窗口:在满足特定条件时更新结果
滑动窗口与哈希表的结合
滑动窗口算法经常与哈希表结合使用,用于记录窗口内元素的出现次数或其他统计信息。
// 使用哈希表记录字符出现位置的示例
int lengthOfLongestSubstringOptimized(char* s) {
int left = 0, right = 0;
int maxLen = 0;
int hash[256]; // 记录字符最近出现的位置
memset(hash, -1, sizeof(hash));
while (right < strlen(s)) {
char c = s[right];
// 如果字符已在窗口中,快速移动左边界
if (hash[c] != -1 && hash[c] >= left) {
left = hash[c] + 1;
}
hash[c] = right; // 更新字符位置
maxLen = fmax(maxLen, right - left + 1);
right++;
}
return maxLen;
}

1万+

被折叠的 条评论
为什么被折叠?



