【每日算法】Day 12-1:滑动窗口算法精讲——子串/子数组问题的优化利器(C++实现)

news2025/4/1 10:35:19

攻克子串问题的效率密码!今日深入解析滑动窗口算法的核心思想与实战技巧,覆盖最小覆盖子串、最长无重复子串等高频场景,彻底掌握O(n)时间复杂度的窗口滑动艺术。


一、滑动窗口核心思想

滑动窗口(Sliding Window) 是一种通过维护动态窗口解决子串/子数组问题的算法,核心特性:

  1. 双指针驱动:左指针收缩窗口,右指针扩展窗口

  2. 窗口单调性:窗口滑动过程保持单向性

  3. 哈希表辅助:快速统计窗口内元素状态

适用场景:

  • 连续子数组/子串的最值问题

  • 统计满足特定条件的区间数量

  • 字符串匹配与模式查找

与暴力解法的对比优势:

方法时间复杂度空间复杂度适用数据规模
暴力枚举O(n²)O(1)n < 10³
滑动窗口O(n)O(k)n ≤ 10⁶

二、滑动窗口模板(C++)

通用模板结构
void slidingWindow(string s) {
    unordered_map<char, int> window; // 窗口状态记录
    int left = 0, right = 0;         // 双指针
    
    while (right < s.size()) {
        char c = s[right];
        right++;                    // 右扩窗口
        window[c]++;                // 更新状态
        
        while (窗口需要收缩条件) {   // 左缩窗口
            char d = s[left];
            left++;
            window[d]--;            // 更新状态
        }
        
        // 更新全局结果(位置取决于具体问题)
    }
}
关键参数:
  • 窗口状态记录:哈希表/数组记录字符频率

  • 收缩条件:触发窗口调整的阈值条件

  • 结果更新时机:根据问题需求在适当位置更新结果


三、四大高频应用场景

场景1:最小覆盖子串(LeetCode 76)
string minWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    
    int left = 0, right = 0;
    int valid = 0; // 匹配字符数
    int start = 0, len = INT_MAX;
    
    while (right < s.size()) {
        char c = s[right++];
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }
        
        while (valid == need.size()) { // 满足条件时收缩
            if (right - left < len) {  // 更新最小窗口
                start = left;
                len = right - left;
            }
            char d = s[left++];
            if (need.count(d)) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
        }
    }
    return len == INT_MAX ? "" : s.substr(start, len);
}
场景2:最长无重复子串(LeetCode 3)
int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> window;
    int left = 0, right = 0, max_len = 0;
    
    while (right < s.size()) {
        char c = s[right++];
        window[c]++;
        
        while (window[c] > 1) { // 出现重复
            char d = s[left++];
            window[d]--;
        }
        
        max_len = max(max_len, right - left);
    }
    return max_len;
}
场景3:长度最小的子数组(LeetCode 209)
int minSubArrayLen(int target, vector<int>& nums) {
    int left = 0, sum = 0, min_len = INT_MAX;
    for (int right = 0; right < nums.size(); ++right) {
        sum += nums[right];
        while (sum >= target) { // 满足条件时收缩
            min_len = min(min_len, right - left + 1);
            sum -= nums[left++];
        }
    }
    return min_len == INT_MAX ? 0 : min_len;
}
场景4:字符串排列(LeetCode 567)
bool checkInclusion(string s1, string s2) {
    unordered_map<char, int> need, window;
    for (char c : s1) need[c]++;
    
    int left = 0, right = 0, valid = 0;
    while (right < s2.size()) {
        char c = s2[right++];
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }
        
        while (right - left >= s1.size()) { // 窗口大小固定
            if (valid == need.size()) return true;
            char d = s2[left++];
            if (need.count(d)) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
        }
    }
    return false;
}

四、滑动窗口变种与优化

变种类型特点经典问题
固定窗口窗口大小固定字符串排列、滑动窗口平均值
可变窗口窗口大小动态变化最小覆盖子串、最长子串
哈希表优化用数组替代哈希表加速ASCII字符集问题
前缀和+窗口结合前缀和数组处理子数组和和等于k的最长子数组

五、大厂真题实战

真题1:最大连续1的个数 III(某大厂2024面试)

题目描述:
二进制数组最多翻转k个0为1,求最长连续1子数组
滑动窗口解法:

int longestOnes(vector<int>& nums, int k) {
    int left = 0, max_len = 0, zero_count = 0;
    for (int right = 0; right < nums.size(); ++right) {
        if (nums[right] == 0) zero_count++;
        
        while (zero_count > k) { // 窗口内0超过k个
            if (nums[left] == 0) zero_count--;
            left++;
        }
        
        max_len = max(max_len, right - left + 1);
    }
    return max_len;
}
真题2:替换后的最长重复字符(LeetCode 424)
int characterReplacement(string s, int k) {
    vector<int> count(26, 0);
    int left = 0, max_count = 0, max_len = 0;
    
    for (int right = 0; right < s.size(); ++right) {
        count[s[right]-'A']++;
        max_count = max(max_count, count[s[right]-'A']);
        
        while (right - left + 1 - max_count > k) { // 需要替换超过k次
            count[s[left]-'A']--;
            left++;
        }
        
        max_len = max(max_len, right - left + 1);
    }
    return max_len;
}

六、复杂度与优化策略

操作时间复杂度空间复杂度优化技巧
窗口滑动O(n)O(1)数组替代哈希表
状态更新O(1)O(k)位运算压缩状态
收缩检测O(1)O(1)记录最大值避免全扫描

七、常见误区与调试技巧

  1. 窗口收缩条件错误:未正确维护窗口有效性(如LeetCode 424中的替换次数计算)

  2. 哈希表更新遗漏:左指针移动时未同步更新状态

  3. 初始化错误:未正确初始化哈希表或计数器

  4. 调试技巧

    • 打印窗口左右边界及内部状态

    • 可视化窗口滑动过程

    • 添加断言检查窗口有效性


LeetCode真题训练:

  • 438. 找到字符串中所有字母异位词

  • 567. 字符串的排列

  • 1004. 最大连续1的个数 III

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2324271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

树莓派超全系列文档--(7)RaspberryOS播放音频和视频

播放音频和视频 播放音频和视频VLC 媒体播放器vlc GUIvlc CLI使用 cvlc 在没有图形用户界面的情况下播放媒体 在 Raspberry Pi OS Lite 上播放音频和视频指定音频输出设备指定视频输出设备同时指定音频和视频输出设备提高数据流播放性能 文章来源&#xff1a; http://raspberr…

chrome浏览器下载和Chrome浏览器的跨域设置

Chrome浏览器的跨域设置 下载chrome浏览器设置chrome跨域 下载chrome浏览器 点击官方下载&#xff0c;然后逐步安装即可 设置chrome跨域 1、然后在D盘创建个文件夹命名为ChromeDevSession。 2、右击chrome浏览器选择属性。 3、在目标编辑栏的最后加上&#xff1a;–disabl…

【高并发内存池】第六弹---深入理解内存管理机制:ThreadCache、CentralCache与PageCache的回收奥秘

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】【项目详解】 目录 1、threadcache回收内存 2、centralcache回收内存 3、pagecache回收内存 1、threadcache回收内…

累积分布策略思路

一种基于概率密度和累积分布函数的量化交易策略&#xff0c;主要应用于期货市场。该策略通过计算价格数据的概率密度和累积分布函数&#xff08;CDF&#xff09;&#xff0c;结合移动平均线和ATR&#xff08;平均真实范围&#xff09;等技术指标&#xff0c;实现多空交易的自动…

【JavaScript】九、JS基础练习

文章目录 1、练习&#xff1a;对象数组的遍历2、练习&#xff1a;猜数字3、练习&#xff1a;生成随机颜色 1、练习&#xff1a;对象数组的遍历 需求&#xff1a;定义多个对象&#xff0c;存数组&#xff0c;遍历数据渲染生成表格 let students [{ name: 小明, age: 18, gend…

RAG、大模型与智能体的关系

一句话总结&#xff1a; RAG&#xff08;中文为检索增强生成&#xff09; 检索技术 LLM 提示。 RAG、大模型与智能体的关系解析 1. 核心概念定义 RAG&#xff08;检索增强生成&#xff09; 是一种结合信息检索与生成式模型的框架&#xff0c;通过从外部知识库&#xff08;如…

Linux中《进程状态--进程调度--进程切换》详细介绍

目录 进程状态Linux内核源代码怎么说运行&&阻塞&&挂起内核链表 进程状态查看Z(zombie)-僵尸进程僵尸进程危害孤儿进程 进程优先级进程切换Linux2.6内核进程O(1)调度队列 进程状态 Linux内核源代码怎么说 为了弄明白正在运⾏的进程是什么意思&#xff0c;我们…

蓝桥杯备考:多米诺骨牌

这道题要求上下方格子和之差要最小&#xff0c;其实就是算每个上下格子的差求和的最小值 这道题其实是动态规划01背包问题 我们直接按步骤做吧 step1:定义状态表示f[i][j]表示从1到i个编号的差值里选出刚好j个数的最小操作次数 step2:推导状态转移方程 如图这就是我们的状态…

AudioFlinger与AudioPoliceManager初始化流程

AF/APF启动流程 在启动AudioSeriver服务的过程中会对启动AF/APF。main_audioserver.cpp有如下代码&#xff1a; AudioFlinger::instantiate();AudioPolicyService::instantiate();AF初始化流程 1.AudioFlinger::instantiate() 1.1 AudioFlinger构造函数 void AudioFlinger:…

网路传输层UDP/TCP

一、端口号 1.端口号 1.1 五元组 端口号(port)标识了一个主机上进行通信的不同的应用程序. 如图所示, 在一个机器上运行着许多进程, 每个进程使用的应用层协议都不一样, 比如FTP, SSH, SMTP, HTTP等. 当主机接收到一个报文中, 网络层一定封装了一个目的ip标识我这台主机, …

Python大数据处理 基本的编程方法

目录 一、实验目的 二、实验要求 三、实验代码 四、实验结果 五、实验体会 一、实验目的 体会基本的python编程方法&#xff1b;学习python中的各类函数&#xff1b;了解python读取与写入文件的方法。 二、实验要求 输入2000年后的某年某月某日&#xff0c;判断这一天是…

STM32F103_LL库+寄存器学习笔记06 - 梳理串口与串行发送“Hello,World“

导言 USART是嵌入式非常重要的通讯方式&#xff0c;它的功能强大、灵活性高且用途广泛。只停留在HAL库层面上用USART只能算是入门&#xff0c;要加深对USART的理解&#xff0c;必须从寄存器层面入手。接下来&#xff0c;先从最简单的USART串行发送开始。 另外&#xff0c;在接…

硬件基础--14_电功率

电功率 电功率:指电流在单位时间内做的功(表示用电器消耗电能快慢的一个物理量)。 单位:瓦特(W)&#xff0c;简称瓦。 公式:PUI(U为电压&#xff0c;单位为V&#xff0c;i为电流&#xff0c;单位为A&#xff0c;P为电功率&#xff0c;单位为W)。 单位换算:进位为1000&#xff…

Vue.js 完全指南:从入门到精通

1. Vue.js 简介 1.1 什么是 Vue.js? Vue.js(通常简称为 Vue)是一个用于构建用户界面的渐进式 JavaScript 框架。所谓"渐进式",意味着 Vue 的设计是由浅入深的,你可以根据自己的需求选择使用它的一部分或全部功能。 Vue 最初由尤雨溪(Evan You)在 2014 年创…

在Git仓库的Readme上增加目录页

一般在编写Readme时想要增加像文章那样的目录&#xff0c;方便快速跳转&#xff0c;但是Markdown语法并没有提供这样的方法&#xff0c;但是可以通过超链接结合锚点的方式来实现&#xff0c;如下图是我之前一个项目里写的Readme&#xff1a; 例如有下面几个Readme内容&#xff…

C# SolidWorks 二次开发 -各种菜单命令增加方式

今天给大家讲一讲solidworks中各种菜单界面&#xff0c;如下图&#xff0c;大概有13处&#xff0c;也许还不完整哈。 1.CommandManager选项卡2.下拉选项卡3.菜单栏4.下级菜单5.浮动工具栏6.快捷方式工具栏7.FeatureManager工具栏区域8.MontionManager区域 ModelView?9.任务窗…

【RocketMQRocketMQ Dashbord】Springboot整合RocketMQ

【RocketMQ&&RocketMQ Dashbord】Springboot整合RocketMQ 【一】Mac安装RocketMQ和RocketMQ Dashbord【1】安装RocketMQ&#xff08;1&#xff09;下载&#xff08;2&#xff09;修改 JVM 参数&#xff08;3&#xff09;启动测试&#xff08;4&#xff09;关闭测试&…

《白帽子讲 Web 安全》之跨站请求伪造

引言 在数字化时代&#xff0c;网络已深度融入人们生活的方方面面&#xff0c;Web 应用如雨后春笋般蓬勃发展&#xff0c;为人们提供着便捷高效的服务。然而&#xff0c;繁荣的背后却潜藏着诸多安全隐患&#xff0c;跨站请求伪造&#xff08;CSRF&#xff09;便是其中极为隐蔽…

K8S学习之基础五十:k8s中pod时区问题并通过kibana查看日志

k8s中pod默认时区不是中国的&#xff0c;挂载一个时区可以解决 vi pod.yaml apiVersion: v1 kind: Pod metadata:name: counter spec:containers:- name: countimage: 172.16.80.140/busybox/busybox:latestimagePullPolicy: IfNotPresentargs: [/bin/sh,-c,i0;while true;do …

nginx代理前端请求

一&#xff0c;项目配置 我在 ip 为 192.168.31.177 的机器上使用 vue3 开发前端项目&#xff0c;项目中使用 axios 调用后端接口。 这是 axios 的配置&#xff1a; import axios from axios;const request axios.create({baseURL: http://192.168.31.177:8001,// 设置请求…