leetcode-滑动窗口总结
滑动窗口是我在刷题时感觉比较困难的部分,简单做一个总结,防止之后又忘了:
一般模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 注意:java 代码由 chatGPT🤖 根据我的 cpp 代码翻译,旨在帮助不同背景的读者理解算法逻辑。 // 本代码还未经过力扣测试,仅供参考,如有疑惑,可以参照我写的 cpp 代码对比查看。 /* 滑动窗口算法框架 */ void slidingWindow(String s) { // 用合适的数据结构记录窗口中的数据 Map<Character, Integer> window = new HashMap<Character, Integer>(); int left = 0 , right = 0 ; while (right < s.length()) { // c 是将移入窗口的字符 char c = s.charAt(right); window.put(c, window.getOrDefault(c, 0 ) + 1 ); // 增大窗口 right++; // 进行窗口内数据的一系列更新 ... /*** debug 输出的位置 ***/ // 注意在最终的解法代码中不要 print // 因为 IO 操作很耗时,可能导致超时 System.out.printf( "window: [%d, %d)\n" , left, right); /********************/ // 判断左侧窗口是否要收缩 while (left < right && window needs shrink) { // d 是将移出窗口的字符 char d = s.charAt(left); window.put(d, window.get(d) - 1 ); // 缩小窗口 left++; // 进行窗口内数据的一系列更新 ... } } } |
但其实上面的模板看看就得了,其实这类题目写法可以参考下面的题目,通过对比几道题目就能发现滑动窗口题目的共通之处。(重点在于需要创建一个表示window的数据结构--通常可以使用HashMap表示元素与次数的对应,特殊情况如题目明确表示为26个小写字母时,可以采用固定长度的数组表示以提高效率--以及窗口左右两个指针。)
重点需要思考以下三个问题:
1、什么时候应该移动 right
扩大窗口?窗口加入字符时,应该更新哪些数据?
2、什么时候窗口应该暂停扩大,开始移动 left
缩小窗口?从窗口移出字符时,应该更新哪些数据?
3、我们要的结果应该在扩大窗口时还是缩小窗口时进行更新?
滑动窗口的题目不能简单理解为扩大窗口+缩小窗口,其实是分几类情况的,我一一阐述:
I. 不定长滑动窗口类:
1. 求最小子串问题:
这类题目通常需要采用扩大窗口+缩小窗口的范式,典型的题目有力扣题有76. 最小覆盖子串 - 力扣(LeetCode):
这道题显然可以直接套用模板,在设置window的情况下,用一个哈希表(need)来记录需求(这里有一个小trick是采用了一个count来记录当前窗口内的子串是否符合条件,确认需求的复杂度可以变成O(1)),代码如下:
import java.util.HashMap; import java.util.Map; class Solution { public String minWindow(String s, String t) { //res用来记录最小子串的长度 int res = Integer.MAX_VALUE; Map<Character, Integer> need = new HashMap<>(); int count = 0; int min_start = 0; //初始化need for(char c : t.toCharArray()){ need.put(c, need.getOrDefault(c, 0) + 1); count++; } int i = 0; for(int j = 0; j < s.length(); j++){ if(need.containsKey(s.charAt(j))){ if(need.get(s.charAt(j)) > 0) count--; need.put(s.charAt(j), need.get(s.charAt(j)) - 1); } while(count == 0){ if(need.containsKey(s.charAt(i))){ need.put(s.charAt(i), need.get(s.charAt(i)) + 1); if(need.get(s.charAt(i)) > 0) count++; if(j - i + 1 < res) { res = j - i + 1; min_start = i; } } i++; } } return res == Integer.MAX_VALUE ? "" : s.substring(min_start, min_start + res); } }
II. 定长滑动窗口类
对于判断字符串s1中是否包含字符串s2的排列或者是异位词问题,可以采用固定长度的滑动窗口。一般的套路都是先初始化窗口,然后再滑动判断。
直接套模板,比较tricky的还是如何在满足条件判断上,这里通过统计s1中不同的字母个数以及设置一个valid变量记录当前窗口满足条件的字母的类别总数以判断是否满足条件。
class Solution { //套模板 public boolean checkInclusion(String s1, String s2) { char[] need = new char[26]; char[] window = new char[26]; int size = 0, valid = 0; //初始化need for(char c : s1.toCharArray()){ if(need[c - 'a'] == 0) size++; need[c - 'a'] += 1; } int left = 0, right = 0; //左闭右开 //先初始化固定窗口,再移动窗口 while(right < s2.length()){ //右指针右移动 char temp1 = s2.charAt(right); if(need[temp1 - 'a'] > 0){ window[temp1 - 'a'] += 1; if(window[temp1 - 'a'] == need[temp1 - 'a']) valid++; } right++; //当满足right - left == s1.length()时,窗口固定 if(right - left == s1.length()){ //左指针右移 if(valid == size) return true; char temp2 = s2.charAt(left); if(need[temp2 - 'a'] > 0){ if(window[temp2 - 'a'] == need[temp2 - 'a']) valid--; window[temp2 - 'a'] -= 1; } left++; } } return false; } }
2. 438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
直接上代码,和刚才基本一样:
class Solution { //之前解法太复杂,本质上就是个固定滑动窗口问题 //直接上模板!!! public List<Integer> findAnagrams(String s, String p) { List<Integer> res = new LinkedList<>(); char[] need = new char[26]; char[] window = new char[26]; int size = 0, valid = 0; //初始化need for(char c : p.toCharArray()){ if(need[c - 'a'] == 0) size++; need[c - 'a'] += 1; } int left = 0, right = 0; while(right < s.length()){ char temp1 = s.charAt(right); if(need[temp1 - 'a'] > 0){ window[temp1 - 'a'] += 1; if(window[temp1 - 'a'] == need[temp1 - 'a']) valid++; } right++; if(right - left == p.length()){ if(valid == size) res.add(left); char temp2 = s.charAt(left); if(need[temp2 - 'a'] > 0){ if(window[temp2 - 'a'] == need[temp2 - 'a']) valid--; window[temp2 - 'a'] -= 1; } left++; } } return res; } }
III. 最长子串问题
1. 3. 无重复字符的最长子串 - 力扣(LeetCode)
本质上也不定长度的滑动窗口问题,但是因为是求最长子串而不是最小子串,所以更新答案的位置在外循环结束前。
class Solution { public int lengthOfLongestSubstring(String s) { //不再需要need Map<Character, Integer> window = new HashMap<>(); int right = 0, left = 0; boolean valid = true; int res = 0; while(right < s.length()){ char temp1 = s.charAt(right); window.put(temp1, window.getOrDefault(temp1, 0) + 1); right++; //发现不满足条件了就得左边右移 while(window.get(temp1) > 1){ char temp2 = s.charAt(left); window.put(temp2, window.get(temp2) - 1); left++; } //在这里更新答案。。。。。。 res = Math.max(res, right - left); } return res; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理