当前位置: 首页 > news >正文

为什么滑动窗口总能把人写红温?

哈喽大家好,我是程序员脚趾

算法里有一种东西特别恶心。

你看题解的时候:

哦~ 原来是这样。

等自己打开 LeetCode:

left++ 还是 right++ 来着?

然后开始:

  • 调半小时

  • 输出一屏 debug

  • 最后发现 valid 少减了一次

没错,我说的就是滑动窗口。

很多人第一次学这个算法,都有一种错觉:

“我会了。”

实际上:

“你只是看会了。”

真正自己写的时候:

while (right < s.length()) {

刚敲出来,人已经开始慌了。

这篇文章咱就不搞那种教科书式废话了。

什么:

滑动窗口是一种在线性时间内解决子串问题的算法思想。

这种话看了和没看一样。

咱今天直接用人话聊。

你读完至少能做到一件事:

以后再碰到子串问题,不会第一时间原地去世。

顺便还能解决这几个经典题:

LeetCode题目难度
76最小覆盖子串Hard
567字符串排列Medium
438找所有字母异位词Medium
3无重复字符最长子串Medium

一、滑动窗口到底是个啥

先问你个问题。

如果让你找:

一个数组里满足条件的连续子数组

你会咋写?

大部分人的第一反应:

for (...) { for (...) { } }

暴力枚举。

简单,直接,且超时。

时间复杂度直接干到O(N²)

LeetCode 看了都摇头。

于是聪明人就发现:

很多区间,其实没必要重复计算。

比如:

[left, right]

已经算过了。

下一次你变成:

[left, right + 1]

其实只多了一个元素。

那我干嘛重新算一遍?

于是滑动窗口就诞生了。

说白了:

用一个会移动的区间,维护当前答案。

窗口右边负责扩张。

窗口左边负责收缩。

像极了一只毛毛虫。

一会伸长。

一会缩短。

然后边爬边找答案。


二、为什么它是 O(N)

很多人看到这个:

while () { while () { } }

立马PTSD发作:

双 while? 那不还是 O(N²)?

其实不是。

因为:

left 和 right 都不会后退。

它俩这一生只有一个梦想:

向右走。

每个元素:

  • 最多进窗口一次

  • 最多出窗口一次

所以整体复杂度其实是O(N)

这就是滑动窗口最牛逼的地方。


三、滑动窗口万能模板

这玩意其实是有固定套路的。

以后你刷题,别硬想。

直接默写模板。

int left = 0, right = 0; while (right < s.length()) { // 1. 右边字符进入窗口 char c = s.charAt(right); right++; // 2. 更新窗口数据 // 3. 判断是否需要收缩 while (窗口不合法) { char d = s.charAt(left); left++; // 4. 更新窗口数据 } // 5. 更新答案 }

别看简单。

实际上整个滑动窗口,核心就三件事:

1、什么时候扩大窗口 2、什么时候收缩窗口 3、什么时候更新答案

你只要把这三个问题想明白。

代码基本不会错。

真正容易死的地方其实是:

答案到底在哪更新?

有的题:

  • 扩张时更新

  • 收缩时更新

  • 收缩结束后更新

第一次写的时候特别容易脑子打结。


四、最经典的一题:最小覆盖子串

题目大概意思:

给你:

s = "ADOBECODEBANC" t = "ABC"

让你找:

s 里最短的一个子串

要求它必须包含:

A、B、C

答案:

BANC

这题第一次做的时候,很多人都会陷入一种状态:

我知道是滑动窗口。 但我不知道窗口里该存啥。

于是开始瞎写。

然后 valid 当场爆炸。


五、need 和 window 到底是干啥的

这个地方其实特别简单。

你只需要记住:

need:目标欠款 window:当前余额

比如:

t = "AABC"

那么:

need: A -> 2 B -> 1 C -> 1

意思就是:

A 还欠两个 B 欠一个 C 欠一个

而 window 就表示:

当前窗口里有多少。

窗口右边扩张:

window.put(c, window.getOrDefault(c, 0) + 1);

窗口左边收缩:

window.put(d, window.get(d) - 1);

整个算法本质上:

就是在讨债。

欠款补齐了。

窗口合法。

开始缩。

缩到快不合法的时候停下。


六、valid 到底是什么鬼

这玩意第一次看特别抽象。

其实它翻译成人话就一句:

当前有几个字符已经还清贷款了。

比如:

need: A -> 1 B -> 1 C -> 1

当前窗口:

A -> 1 B -> 1 C -> 0

那说明:

A 和 B 已经达标

于是:

valid = 2

当:

valid == need.size()

说明:

所有贷款全部还完

窗口合法。

开始收缩。


七、最容易写错的地方

很多人会问:

为啥更新答案放在 while 里面?

因为:

窗口合法的时候,才是候选答案。

而我们要的是:

最短。

所以窗口一合法:

立马开始压缩。

就像搬家收纳。

能扔的全扔。

直到再扔就不合法了。

这时候留下的。

才是当前最优解。


八、完整代码

class Solution { public String minWindow(String s, String t) { Map<Character, Integer> need = new HashMap<>(); Map<Character, Integer> window = new HashMap<>(); for (char c : t.toCharArray()) { need.put(c, need.getOrDefault(c, 0) + 1); } int left = 0, right = 0; int valid = 0; int start = 0; int len = Integer.MAX_VALUE; while (right < s.length()) { char c = s.charAt(right); right++; if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) { valid++; } } while (valid == need.size()) { if (right - left < len) { start = left; len = right - left; } char d = s.charAt(left); left++; if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) { valid--; } window.put(d, window.get(d) - 1); } } } return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); } }

九、真正的滑动窗口思维

很多人学算法。

喜欢背模板。

其实没啥用。

因为一变题就寄。

真正重要的是:

窗口什么时候合法?

你只要能判断:

  • 什么时候扩张

  • 什么时候收缩

  • 什么时候更新答案

那这个题就已经做出来一半了。

后面的:

  • 字符串排列

  • 找异位词

  • 最长无重复子串

本质上全是这一套。

只是条件变了而已。


十、最后送你一句话

我后来发现。

滑动窗口其实就一句话:

right 负责把答案搞出来。
left 负责把答案榨干。

窗口不合法:

right 往前冲。

窗口合法:

left 开始疯狂压缩。

直到榨不动。

这时候留下的。

就是当前最优解。

很多题你一旦用这个视角去看。

会突然发现:

卧槽,原来全长一个样。

这才是滑动窗口真正的套路。

http://www.rkmt.cn/news/1291581.html

相关文章:

  • 除了 Docker 还能用什么?一文看懂容器技术的“四大门派”
  • MusicGPT:基于大语言模型的AI音乐导师项目架构与实现
  • LED驱动设计核心:从欧姆定律到PWM调光,详解限流电阻计算与亮度控制
  • 基于MQTT与CircuitPython打造桌面级3D打印机状态监控终端
  • 用电路贴纸制作互动发光笔记本:零焊接电子工艺入门指南
  • 快速迭代的 AI 应用项目如何借助 Taotoken 实现模型热切换与降级
  • AutoHotkey V2扩展库:从脚本小子到全能开发者的进化之路
  • 如何在不同终端里面使用claude code并使用不同模型
  • 观察使用Taotoken Token Plan套餐后月度API成本的变化趋势
  • 一对老金耳环引发的折腾:在绍兴,我最终选了福正美 - 福正美黄金回收
  • D2DX暗黑2宽屏补丁:3分钟让经典游戏焕发新生的终极优化方案
  • DIY蓝牙街机摇杆:从零打造无线复古游戏控制器
  • 微软 TTS 如何在顶伯中实现自然韵律与停顿
  • 从科学计算到AI训练:CPU的AVX512与GPU的Tensor Core,谁才是低精度计算的王者?
  • 告别显卡焦虑:手把手教你用llama.cpp在MacBook Air上跑通7B中文大模型
  • 基于大语言模型的强化学习奖励函数自动生成:text2reward项目实践指南
  • 小盲区、大智慧:大禹电子双探头传感器助力垃圾精细化管理
  • 企业培训落地难?避开7大误区,企学宝5大策略让培训真正产生价值
  • idea里创建maven的web项目
  • Nginx远程代码执行漏洞
  • 在频繁的模型调用中体会Taotoken聚合路由对稳定性的提升
  • 如何选择专业学术服务提升论文投稿成功率
  • 免费在线 AVIF 转 WebP 工具推荐|无需上传、批量转换、保护隐私的高效图片格式解决方案
  • 3大技术优势:AEUX如何实现Sketch/Figma到After Effects的无缝设计转换
  • 基于DocFX与CI/CD构建.NET私有NuGet包文档一体化管理方案
  • 【RT-DETR实战】038、小目标检测改进:上下文信息增强模块
  • 开源大模型适配器Basaran:一键兼容OpenAI API,无缝集成私有化部署
  • 湖州老金料回炉记:跑六家店,福正美让我把旧镯子留下 - 福正美黄金回收
  • DockDoor:重新定义macOS窗口管理体验的智能预览工具
  • VS Code光标主题资源库:提升开发体验的个性化光标解决方案