下篇:伸缩魔法!进阶吃透可变滑动窗口篇

一、进阶趣味原理:会伸缩的动态窗户

上篇的固定窗口是「定死大小的玻璃窗」,只能整体平移;下篇的可变滑动窗口是「可拉伸的橡皮筋窗户」!

依旧用蜗牛场景升级理解:蜗牛的窗户可以自由变大、缩小,遇到合法元素就拉长身体(右移right),遇到非法元素就收缩尾巴(右移left),窗口大小动态变化,适配更复杂的算法题型。

核心适用场景:寻找「最长子串、最短子数组、满足条件的连续区间」等无固定长度的问题,是面试高频考点。

二、可变滑动窗口通用模板(C++)

cpp 复制代码
int left = 0;
for(int right = 0; right < nums.size(); right++){
    // 1. 纳入右指针元素,更新窗口状态
    // 2. 窗口不合法时,循环收缩左边界
    while(窗口不合法条件){
        // 剔除左指针元素,更新窗口状态
        left++;
    }
    // 3. 窗口合法,更新最优答案(最长/最短/总数)
}

三、下篇4个实战案例(逐行代码详解)

案例1:无重复字符的最长子串(面试必刷)

题目描述:给定字符串,找出其中不含有重复字符的最长子串长度

解题思路:可变窗口,右指针探索新字符,出现重复则收缩左窗口,保证窗口内无重复

cpp 复制代码
#include<iostream>
#include<string>
#include<unordered_set>
#include<algorithm>
using namespace std;

int lengthOfLongestSubstring(string s) {
    unordered_set<char> st; // 存储当前窗口内的字符
    int left = 0, maxLen = 0;
    for(int right = 0; right < s.size(); right++){
        // 出现重复字符,收缩左窗口,直到无重复
        while(st.find(s[right]) != st.end()){
            st.erase(s[left]);
            left++;
        }
        st.insert(s[right]); // 纳入新字符
        maxLen = max(maxLen, right - left + 1); // 更新最大长度
    }
    return maxLen;
}

int main(){
    string s = "abcabcbb";
    cout << lengthOfLongestSubstring(s) << endl; // 输出3
    return 0;
}

代码详解

  1. 用哈希集合st存储当前窗口所有字符,快速判断是否重复;

  2. 右指针遍历字符,若当前字符已在集合中,说明窗口重复,持续删除左边界字符、右移左指针;

  3. 窗口无重复后,纳入当前字符,更新最长子串长度;

  4. 窗口大小动态伸缩,完美适配无固定长度的最优解查找。

案例2:最小长度子数组和(最短窗口问题)

题目描述:给定正整数数组和目标值target,找出和≥target的最短连续子数组,返回其长度,无则返回0

解题思路:可变窗口,右指针累加和,满足条件后不断收缩左窗口,寻找最短区间

cpp 复制代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int minSubArrayLen(int target, vector<int>& nums) {
    int left = 0, sum = 0, minLen = INT_MAX;
    for(int right = 0; right < nums.size(); right++){
        sum += nums[right];
        // 满足条件,尝试收缩窗口找更短区间
        while(sum >= target){
            minLen = min(minLen, right - left + 1);
            sum -= nums[left];
            left++;
        }
    }
    return minLen == INT_MAX ? 0 : minLen;
}

int main(){
    vector<int> nums = {2,3,1,2,4,3};
    cout << minSubArrayLen(7, nums) << endl; // 输出2
    return 0;
}

代码详解

  1. sum累加右指针元素,当和≥目标值时,窗口合法;

  2. 核心逻辑:合法后持续收缩左窗口,不断刷新最短长度;

  3. 收缩过程中实时减去左边界值,保证sum实时更新;

  4. 最终判断是否找到合法窗口,无则返回0,有则返回最短长度。

案例3:至多两个不同字符的最长子串(中等进阶)

题目描述:给定字符串,找出最多包含2个不同字符的最长子串长度

解题思路:哈希表统计窗口字符种类,种类超2则收缩左窗口,动态维护合法区间

cpp 复制代码
#include<iostream>
#include<string>
#include<unordered_map>
#include<algorithm>
using namespace std;

int lengthOfTwoCharSub(string s) {
    unordered_map<char, int> mp; // 统计字符出现次数
    int left = 0, maxLen = 0;
    for(int right = 0; right < s.size(); right++){
        mp[s[right]]++; // 纳入新字符
        // 字符种类超过2,收缩窗口
        while(mp.size() > 2){
            mp[s[left]]--;
            if(mp[s[left]] == 0){
                mp.erase(s[left]); // 次数为0,移除字符
            }
            left++;
        }
        maxLen = max(maxLen, right - left + 1);
    }
    return maxLen;
}

int main(){
    string s = "eceba";
    cout << lengthOfTwoCharSub(s) << endl; // 输出3
    return 0;
}

代码详解

  1. 哈希表mp记录窗口内每个字符的出现频次,mp.size()代表字符种类数;

  2. 右指针纳入新字符后,若种类超2,循环剔除左边界字符;

  3. 若左边界字符频次归零,从哈希表中删除,保证种类统计准确;

  4. 窗口合法后更新最大长度,实现动态伸缩求解。

案例4:最小覆盖子串(困难压轴、面试高频)

题目描述:给字符串s、t,找出s中包含t所有字符的最小子串,无则返回空串

解题思路:双哈希表统计字符匹配情况,右指针探索全覆盖窗口,左指针收缩找最小区间

cpp 复制代码
#include<iostream>
#include<string>
#include<unordered_map>
#include<climits>
using namespace std;

string minWindow(string s, string t) {
    unordered_map<char, int> need, window;
    // 统计t中需要的字符及数量
    for(char c : t) need[c]++;
    int left = 0, valid = 0;
    int start = 0, minLen = INT_MAX;

    for(int right = 0; right < s.size(); right++){
        char cur = s[right];
        if(need.count(cur)){
            window[cur]++;
            // 当前字符数量达标,有效匹配数+1
            if(window[cur] == need[cur]) valid++;
        }

        // 所有字符匹配,窗口全覆盖,尝试收缩
        while(valid == need.size()){
            // 更新最小窗口
            if(right - left + 1 < minLen){
                minLen = right - left + 1;
                start = left;
            }
            // 收缩左边界
            char leftCur = s[left];
            if(need.count(leftCur)){
                if(window[leftCur] == need[leftCur]) valid--;
                window[leftCur]--;
            }
            left++;
        }
    }
    return minLen == INT_MAX ? "" : s.substr(start, minLen);
}

int main(){
    string s = "ADOBECODEBANC", t = "ABC";
    cout << minWindow(s, t) << endl; // 输出BANC
    return 0;
}

代码详解

  1. need存储目标字符串t的字符需求,window存储当前窗口字符;

  2. valid统计完全匹配的字符种类,等于t的字符种类时,窗口全覆盖;

  3. 全覆盖后收缩左窗口,不断刷新最小窗口长度和起始位置;

  4. 收缩时若某个字符匹配失效,减少有效匹配数,终止收缩;

  5. 最终根据记录的起始位置和最小长度,截取结果子串。

四、上下篇核心总结(思维升华)

  • 上篇固定窗口:窗口大小恒定,先成型、再滑动,适合定长区间统计、最值查找,逻辑简单、容错率高;

  • 下篇可变窗口:窗口动态伸缩,先扩张、再收缩,适合最优区间求解(最长/最短),适配绝大多数算法面试题;

  • 通用思维:所有滑动窗口都是「右指针探索,左指针修正」,全程单次遍历,摒弃暴力双层循环,是区间问题的最优解思路。

谢谢

相关推荐
txzrxz1 小时前
关于二维/多维前缀和
算法
来一碗刘肉面1 小时前
顺序表的按值查找
数据结构·c++·算法
zzz_23682 小时前
【Java实习面试算法冲刺】图论
java·算法·面试
大鱼>2 小时前
宠物监控数据安全与隐私保护:端到端加密与合规实践
人工智能·深度学习·算法·iot·宠物
被自己蠢哭2 小时前
BFS广度优先搜索问题:#P00496. 华为od机试—自动泊车
算法
:-)2 小时前
分支限界算法
算法
小肝一下2 小时前
2. 顺序表和链表
c语言·数据结构·c++·链表·dijkstra·依蕾娜·香香的依蕾娜
大鱼>3 小时前
宠物行为识别AI算法详解:从数据采集到模型部署的完整方案
人工智能·深度学习·算法·宠物
王莎莎-MinerU3 小时前
解析回归集:RAG 和 Agent 上线前,先把表格、公式、版面测清楚
人工智能·算法·数据挖掘·回归·pdf·ocr