算法刷题笔记:滑动窗口两大经典题
本篇针对LeetCode 3.无重复字符的最长子串 、LeetCode 1004.最大连续1的个数 III ,只保留最优滑动窗口解法,思路逐句拆解
一、无重复字符的最长子串(LeetCode 3)
题目描述
给定一个字符串 s,请你找出其中不含有重复字符的最长子串的长度。
为什么用滑动窗口?
- 求最长连续子串 ,是典型的区间类最优解问题。
- 暴力枚举所有子串时间复杂度 O(n²),滑动窗口可做到 O(n)。
- 滑动窗口能保证:窗口内永远满足"无重复字符",我们只需要不断扩大/缩小窗口并记录最大值即可。
详细解题思路
-
定义窗口规则
我们维护一个滑动窗口
[left, right],保证:- 窗口内所有字符只出现一次
- right 是右边界,负责扩大窗口(进新字符)
- left 是左边界,负责缩小窗口(踢出重复字符)
-
用哈希表统计次数
用大小为 128 的数组
hash[]记录每个字符在窗口内出现的次数,因为 ASCII 字符共 128 个,数组比 map 更快。 -
右指针进窗口
让 right 从左到右遍历每个字符:
- 把当前字符
s[right]加入窗口,hash[s[right]]++
- 把当前字符
-
判断是否出现重复
如果当前字符的次数 > 1,说明窗口内出现重复:
- 必须移动 left 缩小窗口,直到重复消失
- 每移动一次 left,就把离开窗口的字符次数减一
-
更新最长长度
每次窗口合法(无重复)时,计算窗口长度
right-left+1,更新最大值。 -
继续移动 right,直到遍历结束
C++ 最优代码
cpp
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = { 0 }; // 统计每个字符出现的次数
int left = 0, right = 0; // 滑动窗口左右边界
int n = s.size();
int ret = 0; // 保存最长长度
while (right < n) {
char in = s[right];
hash[in]++; // 右指针字符进入窗口
// 如果当前字符重复,移动左指针缩小窗口
while (hash[in] > 1) {
hash[s[left]]--; // 左指针字符离开窗口
left++;
}
// 更新最长无重复子串长度
ret = max(ret, right - left + 1);
right++; // 右指针继续前进
}
return ret;
}
};
复杂度
- 时间:O(n),每个字符最多进窗口一次、出窗口一次
- 空间:O(1),数组大小固定 128,是常数空间
二、最大连续 1 的个数 III(LeetCode 1004)
题目描述
给定一个二进制数组 nums 和一个整数 k,我们可以翻转最多 k 个 0 变为 1,请返回数组中最长连续 1 的子数组长度。
为什么用滑动窗口?
- 求最长连续子数组,区间类最优解 → 滑动窗口标配。
- 翻转最多 k 个 0,等价于:找最长子数组,其中 0 的个数 ≤ k。
- 暴力解法 O(n²),滑动窗口 O(n) 最优。
详细解题思路
-
问题转化(最关键)
不用纠结"翻转",直接转化为:
寻找一段最长的连续子数组,其中 0 的数量不超过 k 。满足这个条件的子数组,就是可以翻转得到全 1 的最长区间。
-
定义窗口规则
维护窗口
[left, right],保证:- 窗口内 0 的个数 ≤ k
- right 进窗口,扩大区间
- 0 超标时 left 出窗口,缩小区间
-
统计窗口内 0 的数量
用变量
zero实时记录窗口里有多少个 0,不用哈希表,更高效。 -
右指针进窗口
right 遍历数组:
- 遇到 0 →
zero++ - 遇到 1 → 不处理
- 遇到 0 →
-
判断 0 是否超标(>k)
如果
zero > k:- 必须移动 left 缩小窗口
- 如果离开窗口的是 0 →
zero--
-
更新最长长度
每次窗口合法时,计算长度并更新最大值。
C++ 最优代码
cpp
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int ret = 0; // 最长长度
int zero = 0; // 窗口内 0 的个数
// 滑动窗口:left 不动,right 一直往前走
for (int left = 0, right = 0; right < nums.size(); right++) {
// 1. 右指针进窗口
if (nums[right] == 0) {
zero++;
}
// 2. 0 超过 k,左指针出窗口
while (zero > k) {
if (nums[left] == 0) {
zero--;
}
left++;
}
// 3. 更新最长长度
ret = max(ret, right - left + 1);
}
return ret;
}
};
复杂度
- 时间:O(n)
- 空间:O(1)
滑动窗口通用思维
所有最长/最短连续子数组/子串问题,都按这 4 步走:
- 把题目条件翻译成窗口规则(如无重复、0 ≤ k、和 ≥ target 等)
- right 一直往前走,进窗口
- 不满足规则时,left 往前走,出窗口
- 每次满足规则时,更新答案