【LeetCode精选算法】滑动窗口专题二

目录

[13. 水果成篮(904. Fruit Into Baskets)](#13. 水果成篮(904. Fruit Into Baskets))

[14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String)](#14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String))

[15. 串联所有单词的子串(30. Substring with Concatenation of All Words)](#15. 串联所有单词的子串(30. Substring with Concatenation of All Words))

[16. 最小覆盖子串(76. Minimum Window Substring)](#16. 最小覆盖子串(76. Minimum Window Substring))


13. 水果成篮(904. Fruit Into Baskets)

题目链接LeetCode 904

详细解题思路

  1. 核心思想:滑动窗口,维护窗口内水果种类≤2

  2. 问题转化:求最长子数组,其中不同数字个数≤2

  3. 具体步骤

    • 使用哈希表hash记录窗口内每种水果的数量

    • 变量kinds记录窗口内水果种类数

    • 右指针扩展窗口:

      • 如果新水果在窗口中第一次出现,kinds++
    • kinds > 2时,收缩窗口:

      • 左指针水果数量减1

      • 如果减到0,kinds--

    • 更新最大窗口长度

  4. 优化:可以用数组代替哈希表(水果种类有限)

  5. 时间复杂度:O(n),空间复杂度:O(水果种类数)

java 复制代码
class Solution {
    public int totalFruit(int[] fruits) {
        // 水果种类有限,用数组代替哈希表
        int[] basket = new int[fruits.length + 1];  // 假设最大下标
        int kinds = 0;  // 当前篮子中水果种类数
        int left = 0, right = 0;
        int maxFruits = 0;
        
        while (right < fruits.length) {
            // 右指针水果进入篮子
            int fruit = fruits[right];
            if (basket[fruit] == 0) {
                kinds++;  // 新种类水果
            }
            basket[fruit]++;
            
            // 如果种类超过2,收缩窗口
            while (kinds > 2) {
                int outFruit = fruits[left];
                basket[outFruit]--;
                if (basket[outFruit] == 0) {
                    kinds--;  // 该种类水果已全部移除
                }
                left++;
            }
            
            // 更新最大水果数
            maxFruits = Math.max(maxFruits, right - left + 1);
            
            // 右指针右移
            right++;
        }
        
        return maxFruits;
    }
}

14. 找到字符串中所有字母异位词(438. Find All Anagrams in a String)

题目链接LeetCode 438

详细解题思路

  1. 核心思想:固定大小的滑动窗口 + 哈希表统计

  2. 具体步骤

    • 统计p中每个字符的出现次数pCount

    • 维护滑动窗口内字符的出现次数windowCount

    • 维护valid变量表示窗口中满足条件的字符数

    • 窗口大小固定为p的长度

    • 右指针扩展,更新windowCountvalid

    • 当窗口大小等于p长度时:

      • 如果valid == p.length(),找到异位词

      • 左指针收缩,更新windowCountvalid

  3. 时间复杂度:O(n),空间复杂度:O(字符集大小)

java 复制代码
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<>();
        if (s.length() < p.length()) return result;
        
        // 统计p中字符出现次数
        int[] pCount = new int[26];
        for (char c : p.toCharArray()) {
            pCount[c - 'a']++;
        }
        
        // 窗口内字符统计
        int[] windowCount = new int[26];
        int left = 0, right = 0;
        int valid = 0;  // 窗口中满足条件的字符数
        
        while (right < s.length()) {
            // 右指针字符进入窗口
            char inChar = s.charAt(right);
            int idx = inChar - 'a';
            
            // 更新窗口统计
            windowCount[idx]++;
            // 如果该字符在p中,且窗口中出现次数不超过p中次数,valid增加
            if (windowCount[idx] <= pCount[idx]) {
                valid++;
            }
            
            // 当窗口大小等于p长度时
            if (right - left + 1 == p.length()) {
                // 如果valid等于p长度,说明找到异位词
                if (valid == p.length()) {
                    result.add(left);
                }
                
                // 左指针字符移出窗口
                char outChar = s.charAt(left);
                int outIdx = outChar - 'a';
                
                // 更新窗口统计
                if (windowCount[outIdx] <= pCount[outIdx]) {
                    valid--;
                }
                windowCount[outIdx]--;
                
                // 左指针右移
                left++;
            }
            
            // 右指针右移
            right++;
        }
        
        return result;
    }
}

15. 串联所有单词的子串(30. Substring with Concatenation of All Words)

题目链接LeetCode 30

详细解题思路

  1. 核心思想:将单词视为字符,转化为"字母异位词"问题

  2. 具体步骤

    • 统计words中每个单词的出现次数wordCount

    • 单词长度固定为wordLen,总长度totalLen = wordLen * words.length

    • 因为单词可能从s的不同位置开始,需要遍历wordLen个起始位置

    • 对于每个起始位置,使用滑动窗口:

      • 每次取wordLen长度的子串作为一个"字符"

      • 维护窗口内单词的出现次数windowCount

      • 当窗口单词数等于words长度时,检查是否匹配

  3. 时间复杂度:O(n * wordLen),空间复杂度:O(words.length)

java 复制代码
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if (s == null || s.length() == 0 || words == null || words.length == 0) {
            return result;
        }
        
        int wordLen = words[0].length();
        int totalWords = words.length;
        int totalLen = wordLen * totalWords;
        
        // 统计words中单词出现次数
        Map<String, Integer> wordCount = new HashMap<>();
        for (String word : words) {
            wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
        }
        
        // 尝试所有可能的起始位置(0到wordLen-1)
        for (int start = 0; start < wordLen; start++) {
            int left = start;
            int right = start;
            Map<String, Integer> windowCount = new HashMap<>();
            int matched = 0;  // 已匹配的单词数
            
            while (right + wordLen <= s.length()) {
                // 取当前单词
                String word = s.substring(right, right + wordLen);
                right += wordLen;
                
                // 如果单词在words中
                if (wordCount.containsKey(word)) {
                    windowCount.put(word, windowCount.getOrDefault(word, 0) + 1);
                    matched++;
                    
                    // 如果当前单词出现次数超过所需,收缩窗口
                    while (windowCount.get(word) > wordCount.get(word)) {
                        String leftWord = s.substring(left, left + wordLen);
                        windowCount.put(leftWord, windowCount.get(leftWord) - 1);
                        matched--;
                        left += wordLen;
                    }
                    
                    // 如果匹配了所有单词
                    if (matched == totalWords) {
                        result.add(left);
                    }
                } else {
                    // 如果单词不在words中,重置窗口
                    windowCount.clear();
                    matched = 0;
                    left = right;
                }
            }
        }
        
        return result;
    }
}

16. 最小覆盖子串(76. Minimum Window Substring)

题目链接LeetCode 76

详细解题思路

  1. 核心思想:滑动窗口 + 双哈希表

  2. 具体步骤

    • 统计t中每个字符的出现次数need

    • 维护窗口内字符出现次数window

    • 维护valid表示窗口中满足t条件的字符数

    • 右指针扩展窗口:

      • 更新windowvalid
    • valid == need.size()时(窗口中包含t的所有字符):

      • 更新最小子串

      • 左指针收缩窗口,直到valid < need.size()

  3. 时间复杂度:O(n),空间复杂度:O(字符集大小)

java 复制代码
class Solution {
    public String minWindow(String s, String t) {
        if (s == null || t == null || s.length() < t.length()) {
            return "";
        }
        
        // 统计t中字符出现次数
        Map<Character, Integer> need = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        
        // 窗口统计
        Map<Character, Integer> window = new HashMap<>();
        int left = 0, right = 0;
        int valid = 0;  // 窗口中满足need条件的字符数
        int start = 0;  // 最小子串起始位置
        int minLen = Integer.MAX_VALUE;  // 最小子串长度
        
        while (right < s.length()) {
            // 右指针字符进入窗口
            char inChar = s.charAt(right);
            right++;
            
            // 更新窗口统计
            if (need.containsKey(inChar)) {
                window.put(inChar, window.getOrDefault(inChar, 0) + 1);
                if (window.get(inChar).equals(need.get(inChar))) {
                    valid++;
                }
            }
            
            // 当窗口中包含t的所有字符时
            while (valid == need.size()) {
                // 更新最小子串
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }
                
                // 左指针字符移出窗口
                char outChar = s.charAt(left);
                left++;
                
                // 更新窗口统计
                if (need.containsKey(outChar)) {
                    if (window.get(outChar).equals(need.get(outChar))) {
                        valid--;
                    }
                    window.put(outChar, window.get(outChar) - 1);
                }
            }
        }
        
        // 返回结果
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }
}
相关推荐
Filotimo_2 小时前
在java开发中,cron表达式概念
java·开发语言·数据库
Gorgous—l2 小时前
数据结构算法学习:LeetCode热题100-动态规划篇(下)(单词拆分、最长递增子序列、乘积最大子数组、分割等和子集、最长有效括号)
数据结构·学习·算法
码农水水2 小时前
京东Java面试被问:HTTP/2的多路复用和头部压缩实现
java·开发语言·分布式·http·面试·php·wpf
你怎么知道我是队长2 小时前
C语言---未定义行为
java·c语言·开发语言
没有bug.的程序员3 小时前
Java 序列化:Serializable vs. Protobuf 的性能与兼容性深度对比
java·开发语言·后端·反射·序列化·serializable·protobuf
北京地铁1号线3 小时前
2.3 相似度算法详解:Cosine Similarity 与 Euclidean Distance
算法·余弦相似度
圣保罗的大教堂3 小时前
leetcode 1895. 最大的幻方 中等
leetcode
愚公移码3 小时前
蓝凌EKP产品:主文档权限机制浅析
java·前端·数据库·蓝凌