Skip to content

3月12日报(滑动窗口)

约 1305 字大约 4 分钟

日报leetcode

2026-03-12

  • 完成两道滑动窗口的 mid 题目
  • 把 js 的基础知识看完了
  • 浅学了 vue 相关知识

滑动窗口

也是双指针的一种类型,同向双指针

无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。

思路分析

由于我们都要接到一个没有重复元素的子串后面,所以重复的元素一定来自于接入的字符,所以我们使用滑动窗口来进行处理 使用哈希表来去重,以下是代码实现

使用哈希集合(布尔数组)

class Solution {  
    public int lengthOfLongestSubstring(String S) {  
        char[] s = S.toCharArray();  
        int n = s.length;  
        int ans = 0, left = 0;  
        boolean[] has = new boolean[128];  
        for (int right = 0; right < n; right++) {  
            char c = s[right];  
            while (has[c]) {   // 去重 在表中查询到了重复的字母,执行以下
                has[s[left]] = false;   // 移除重复字母 
                left++;       // 移动窗口
            }  
            has[c] = true;  
            ans = Math.max(ans, right - left +1 );  
        }  
        return ans;  
    }  
}

使用哈希表

class Solution {  
    public int lengthOfLongestSubstring(String S) {  
        char[] s = S.toCharArray(); // 转换成 char[] 加快效率(忽略带来的空间消耗)  
        int n = s.length;  
        int ans = 0;  
        int left = 0;  
        int[] cnt = new int[128]; // 也可以用 HashMap<Character, Integer>,这里为了效率用的数组  
        for (int right = 0; right < n; right++) {  
            char c = s[right];  
            cnt[c]++;  
            while (cnt[c] > 1) { // 窗口内有重复字母  
                cnt[s[left]]--; // 移除窗口左端点字母  
                left++; // 缩小窗口  
            }  
            ans = Math.max(ans, right - left + 1); // 更新窗口长度最大值  
        }  
        return ans;  
    }  
}

定长滑窗

窗口右端点在 i 时,由于窗口长度为 k,所以窗口左端点为 i−k+1 所以对于订餐滑窗的题目,我们总结为以下步骤 1. 首先 下标为 i 的元素进入窗口,更新相关统计量。如果左端点 i-k+1 < 0 则证明 第一个窗口还没有形成,不断重复第一步直至形成窗口 2. 更新答案 3. 左端点离开窗口,更新相关统计量,进行下一个循环

我们由一道题目进行引入

定长子串中元音的最大数目

给你字符串 s 和整数 k 。 请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。 英文中的 元音字母 为(aeiou)。

class Solution {  
    public int maxVowels(String S, int k) {  
        char[] s = S.toCharArray();  
        int ans = 0;  
        int vowel = 0;  
        for (int i = 0; i < s.length; i++) {  // 开始枚举 窗口的右端点
        // 更新相关统计数据
            if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u' ) {  
                vowel++;  
            }  
            int left = i - k + 1;  // 左端点
            if (left < 0) {  // 窗口大小不足 k
                continue;    // 回到开头,直到行成第一个窗口
            }  
            ans = Math.max(ans, vowel);  // 更新答案
            if (ans == k) {              // 如果 ans 等于 窗口最大值 
                break;                   // 即已经为答案理论最大值,停止循环
            }  
  
            char out = s[left];   // 左端点离开窗口 
            if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {  
                vowel--;    // 离开窗口也要更新一下统计数据
            }  
        } return ans;  
    }  
}

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

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

思路

在有了定长窗口的思路后,我们可以用这一道题的思路来解答这道题目(也可以用不定长滑窗思路)

如果窗口内字母出现的次数跟 p 中一样,意味着他们是异位词,直接将左端点的下标加入答案即可 看代码注释

解答

定长滑窗

时间复杂度:O(∣Σ∣m+n)

class Solution {  
    public List<Integer> findAnagrams(String s, String p) {  
	    // 统计 p 的每种字母出现的次数
        int[] cntP = new int[26]; 
        for (char c : p.toCharArray()) {  
            cntP[c - 'a']++;  // 统计字母
        }  
  
        List<Integer> ans = new ArrayList<>();  // 新建返回答案的列表
        int[] cntS = new int[26];  // 用来统计 s 字母出现的次数
        for (int right = 0; right < s.length(); right++) {  
            cntS[s.charAt(right) - 'a']++;  // 右端点字母进入窗口 + 更新数据
            int left = right - p.length() + 1;  
            if (left < 0) {  // 窗口长度不足
                continue;  
            }  
            if (Arrays.equals(cntS, cntP)) {  // 对比子串和 p 字母出现的次数相同
                ans.add(left);                // 将左端点的下标添加至答案里
            }  
            cntS[s.charAt(left) - 'a']--;     // 移动左端点(离开窗口 + 更新数据
        }  
        return ans;  
    }  
}

不定长滑窗

我不行了,不定长的做法明天写我要看别的去了 我又行了现在写 该解法与定长思路差不多一致 时间复杂度:O(m+n)

class Solution {  
    public List<Integer> findAnagrams(String s, String p) {  
        int[] cnt = new int[26];  // 统计 p 中的字母出现次数
        for (char c : p.toCharArray()) {  
            cnt[c - 'a']++;  
        }  
  
        List<Integer> ans = new ArrayList<>();  
        int left = 0;  
        for (int right = 0; right < s.length(); right++) {  
            int c = s.charAt(right) - 'a';  
            cnt[c]--;   // 右端点进入窗口
            while (cnt[c] < 0) {  // 字母 c 太多了 要移动窗口
                cnt[s.charAt(left) - 'a']++;  // 左端点移动
                left++;  
            }  
            if (right - left + 1 ==p.length()) {  // 子串和 p 字母出现次数一致
                ans.add(left);                    // 下标添加至答案
            }  
        }  
        return ans;  
    }  
}