滑动窗口:双指针的优雅舞步,征服连续区间问题的利器

目录

滑动窗口算法详解

一、滑动窗口的基本概念

滑动窗口是一种处理连续子数组/子串问题的高效算法,通过维护一个动态的窗口(由两个指针定义),在O(n)时间内解决问题。

二、C++滑动窗口通用模板

基础模板(最小窗口问题)

cpp 复制代码
int left = 0, right = 0;          // 窗口的左右边界
int windowValue = 0;              // 窗口的当前值(如和、种类数等)
int result = INT_MAX;             // 存储最终结果
int n = nums.size();              // 数组长度

while (right < n) {
    // 1. 右指针扩大窗口
    windowValue += process(nums[right]);  // 处理右指针元素
    
    // 2. 当窗口满足条件时,尝试缩小窗口
    while (windowConditionMet(windowValue, target)) {
        // 更新结果
        result = min(result, right - left + 1);
        
        // 左指针缩小窗口
        windowValue -= process(nums[left]);
        left++;
    }
    
    right++;
}

return result == INT_MAX ? 0 : result;  // 处理未找到的情况

三、经典题目代码解析

1. 209. 长度最小的子数组(基础模板)

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int left = 0, right = 0;
        int sum = 0;          // 窗口内元素的和
        int minLen = INT_MAX; // 最小长度
        
        while (right < n) {
            // 1. 右指针移动,扩大窗口
            sum += nums[right];
            
            // 2. 当窗口满足条件(和>=target)
            while (sum >= target) {
                // 更新最小长度
                minLen = min(minLen, right - left + 1);
                
                // 左指针移动,缩小窗口
                sum -= nums[left];
                left++;
            }
            
            right++;
        }
        
        return minLen == INT_MAX ? 0 : minLen;
    }
};

2. 3. 无重复字符的最长子串

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        if (n == 0) return 0;
        
        int left = 0, right = 0;
        int maxLen = 0;
        unordered_set<char> window;  // 用哈希集合记录窗口内的字符
        
        while (right < n) {
            char c = s[right];
            
            // 如果字符已存在,移动左指针直到去除重复
            while (window.count(c)) {
                window.erase(s[left]);
                left++;
            }
            
            // 添加当前字符到窗口
            window.insert(c);
            
            // 更新最大长度
            maxLen = max(maxLen, right - left + 1);
            
            right++;
        }
        
        return maxLen;
    }
};

3. 904. 水果成篮(使用哈希表)

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        unordered_map<int, int> basket;  // 篮子:水果类型 -> 数量
        int left = 0, right = 0;
        int maxFruits = 0;
        
        while (right < n) {
            // 1. 摘取当前水果
            int fruit = fruits[right];
            basket[fruit]++;
            
            // 2. 如果篮子种类超过2,移动左指针
            while (basket.size() > 2) {
                int leftFruit = fruits[left];
                basket[leftFruit]--;
                if (basket[leftFruit] == 0) {
                    basket.erase(leftFruit);
                }
                left++;
            }
            
            // 3. 更新最大水果数
            maxFruits = max(maxFruits, right - left + 1);
            
            right++;
        }
        
        return maxFruits;
    }
};

4. 438. 找到字符串中所有字母异位词(固定窗口)

cpp 复制代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> result;
        if (s.size() < p.size()) return result;
        
        vector<int> pCount(26, 0);  // p的字符频率
        vector<int> windowCount(26, 0);  // 窗口的字符频率
        
        // 统计p的字符频率
        for (char c : p) {
            pCount[c - 'a']++;
        }
        
        // 初始化第一个窗口
        for (int i = 0; i < p.size(); i++) {
            windowCount[s[i] - 'a']++;
        }
        
        // 检查第一个窗口
        if (windowCount == pCount) {
            result.push_back(0);
        }
        
        // 滑动窗口
        for (int right = p.size(); right < s.size(); right++) {
            // 移除左边界字符
            int left = right - p.size();
            windowCount[s[left] - 'a']--;
            
            // 添加右边界字符
            windowCount[s[right] - 'a']++;
            
            // 检查当前窗口
            if (windowCount == pCount) {
                result.push_back(left + 1);
            }
        }
        
        return result;
    }
};

四、滑动窗口的变体和技巧

1. 使用数组代替哈希表(效率更高)

cpp 复制代码
// 针对小写字母问题,用数组代替unordered_map
int hash[26] = {0};  // 比unordered_map快

// 针对ASCII字符
int hash[128] = {0};  // 覆盖所有ASCII字符

2. 计数器的使用(优化判断)

cpp 复制代码
class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0, right = 0;
        int zeroCount = 0;  // 窗口内0的个数
        int maxLen = 0;
        
        while (right < nums.size()) {
            // 统计0的个数
            if (nums[right] == 0) {
                zeroCount++;
            }
            
            // 如果0太多,移动左指针
            while (zeroCount > k) {
                if (nums[left] == 0) {
                    zeroCount--;
                }
                left++;
            }
            
            // 更新最大长度
            maxLen = max(maxLen, right - left + 1);
            
            right++;
        }
        
        return maxLen;
    }
};

五、常见错误和注意事项

1. 边界条件处理

cpp 复制代码
// 错误:忘记处理空数组
if (nums.empty()) return 0;

// 错误:忘记初始化结果
int result = 0;  // 或者INT_MAX/INT_MIN

2. 指针移动时机

cpp 复制代码
// 正确的顺序
while (right < n) {
    // 1. 更新窗口状态
    // 2. 检查条件,可能需要while循环
    // 3. 更新答案
    // 4. 移动右指针
}

// 错误:在更新答案前移动了右指针

3. 整数溢出

cpp 复制代码
// 对于大数组,和可能溢出
long long sum = 0;  // 使用long long

// 计算长度时
int length = right - left + 1;  // 可能为负值
if (length < 0) length = 0;

六、滑动窗口的复杂度分析

  • 时间复杂度:O(n),每个元素最多被访问两次(进窗口和出窗口各一次)
  • 空间复杂度
    • 使用哈希表:O(k),k为字符集大小
    • 使用数组:O(1)或O( C ),C为常数(如26或128)

七、练习题建议

按难度递增顺序练习:

  1. 209.长度最小的子数组(基础)
  2. 3.无重复字符的最长子串(经典)
  3. 1004.最大连续1的个数 III(带约束)
  4. 904.水果成篮(种类限制)
  5. 438.找到字符串中所有字母异位词(固定窗口)
  6. 76.最小覆盖子串(综合应用)
  7. 30.串联所有单词的子串(困难)

掌握滑动窗口的关键是理解窗口何时扩大、何时缩小、何时更新答案这三个核心问题。通过练习这些题目,你会在面试和算法竞赛中游刃有余。

相关推荐
拼命鼠鼠2 小时前
【算法】矩阵链乘法的动态规划算法
算法·矩阵·动态规划
LYFlied2 小时前
【每日算法】LeetCode 17. 电话号码的字母组合
前端·算法·leetcode·面试·职场和发展
式5163 小时前
线性代数(八)非齐次方程组的解的结构
线性代数·算法·机器学习
Nandeska3 小时前
2、数据库的索引与底层数据结构
数据结构·数据库
橘颂TA4 小时前
【剑斩OFFER】算法的暴力美学——翻转对
算法·排序算法·结构与算法
叠叠乐4 小时前
robot_state_publisher 参数
java·前端·算法
hweiyu004 小时前
排序算法:冒泡排序
算法·排序算法
brave and determined4 小时前
CANN训练营 学习(day9)昇腾AscendC算子开发实战:从零到性能冠军
人工智能·算法·机器学习·ai·开发环境·算子开发·昇腾ai
Dave.B4 小时前
用【vtk3DLinearGridCrinkleExtractor】快速提取3D网格相交面
算法·3d·vtk