滑动窗口是处理子串/子数组问题的经典双指针技巧,核心是通过维护一个"窗口"(左右指针界定的区间),动态调整窗口范围来满足题目条件,从而高效求解问题。
一、无重复字符的最长子串
题目描述:
给定一个字符串 s,找出其中不含有重复字符的最长子串的长度。
示例:
- 输入:
s = "abcabcbb",输出:3(最长子串为"abc") - 输入:
s = "bbbbb",输出:1(最长子串为"b")
解题思路:
用滑动窗口维护"无重复字符的子串",配合哈希数组记录字符出现次数:
- 定义窗口左右指针
left、right,哈希数组hash[128]统计窗口内字符出现次数。 - 右指针
right遍历字符串,将当前字符加入窗口并更新出现次数。 - 若当前字符出现次数超过1(窗口内有重复),移动左指针缩小窗口,直到无重复。
- 每次调整后,更新最长子串长度。
完整代码:
cpp
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = {0};
int ret = 0;
for(int left = 0, right = 0; right < s.size(); right++) {
hash[s[right]]++;
while(hash[s[right]] > 1) {
hash[s[left++]]--;
}
ret = max(ret, right - left + 1);
}
return ret;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),每个字符最多被左右指针各遍历一次。
- 空间复杂度:O(1)O(1)O(1),哈希数组大小固定(128个ASCII字符)。
二、长度最小的子数组
题目描述:
给定含 n 个正整数的数组和正整数 target,找出总和≥target的长度最小的子数组,返回其长度;若不存在则返回 0。
示例:
- 输入:
target = 7, nums = [2,3,1,2,4,3],输出:2(子数组[4,3])
解题思路:
用滑动窗口维护"总和≥target的子数组",动态缩小窗口找最小长度:
- 定义窗口左右指针
left、right,变量sum记录窗口内元素和。 - 右指针
right遍历数组,累加元素和到sum。 - 若
sum ≥ target,尝试移动左指针缩小窗口(同时更新最小长度),直到sum < target。 - 遍历结束后,若未找到符合条件的子数组则返回
0。
完整代码:
cpp
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0, len = INT_MAX;
for(int left = 0, right = 0; right < nums.size(); right++) {
sum += nums[right];
while(sum >= target) {
len = min(len, right - left + 1);
sum -= nums[left++];
}
}
return len == INT_MAX ? 0 : len;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),每个元素最多被左右指针各遍历一次。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
三、最大连续1的个数 III
题目描述:
给定二进制数组 nums 和整数 k,最多可以翻转 k 个 0,返回操作后数组中连续 1 的最大个数。
示例:
- 输入:
nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2,输出:6(翻转后最长子数组为[1,1,1,0,0,1,1,1,1,1])
解题思路:
用滑动窗口维护"最多包含 k 个 0 的子数组"(即允许翻转 k 个 0 后的连续1区间):
- 定义窗口左右指针
left、right,变量zeros统计窗口内0的个数。 - 右指针
right遍历数组,遇到0则zeros++。 - 若
zeros > k,移动左指针缩小窗口,直到zeros ≤ k。 - 每次调整后,更新最长子数组长度。
完整代码:
cpp
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int ret = 0;
for(int left = 0, right = 0, zeros = 0; right < nums.size(); right++) {
if(nums[right] == 0) zeros++;
while(zeros > k) {
if(nums[left++] == 0) zeros--;
}
ret = max(ret, right - left + 1);
}
return ret;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),每个元素最多被左右指针各遍历一次。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。
四、将x减到0的最小操作数
题目描述:
给定整数数组 nums 和整数 x,每次操作移除数组最左或最右元素并从 x 中减去该元素值,要求将 x 恰好减到 0,返回最小操作数;否则返回 -1。
示例:
- 输入:
nums = [1,1,4,2,3], x = 5,输出:2(移除后两个元素2+3=5)
解题思路:
转化问题 :"最小操作数"等价于"数组中最长的、和为 sum(nums)-x 的子数组"(因为移除的元素是数组两端,剩余的中间子数组和为 sum-x)。
- 计算数组总和
sum,目标子数组和为target = sum - x(若target < 0,直接返回-1)。 - 用滑动窗口找最长的、和为
target的子数组。 - 若找到该子数组,最小操作数为
数组长度 - 子数组长度;否则返回-1。
完整代码:
cpp
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int sum = 0;
for(auto n : nums) sum += n;
int target = sum - x;
if(target < 0) return -1;
int ret = -1;
for(int left = 0, right = 0, tmp = 0; right < nums.size(); right++) {
tmp += nums[right];
while(tmp > target) tmp -= nums[left++];
if(tmp == target)
ret = max(ret, right - left + 1);
}
return ret == -1 ? -1 : nums.size() - ret;
}
};
复杂度分析:
- 时间复杂度:O(n)O(n)O(n),每个元素最多被左右指针各遍历一次。
- 空间复杂度:O(1)O(1)O(1),仅用常数级额外变量。