【LeetCode算法成长之路】滑动窗口算法总结与经典题目分析

前言

在这里插入图片描述

本文小新为大家带来 滑动窗口算法 相关知识,经过对滑动窗口算法类题目的总结,为大家分享滑动窗口算法概述(包括:滑动窗口算法思想滑动窗口算法使用场景滑动窗口算法使用思路),滑动窗口算法代码模板,以及两个经典例题(长度最小的子数组最小覆盖子串),帮助大家更好的理解与掌握滑动窗口算法~

不积跬步,无以至千里;不积小流,无以成江海。每天进步一点点,在成为强者的路上,小新与大家共同成长!

博主主页:小新要变强 的主页
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)


目录

文章标题

  • 前言
  • 目录
  • 一、滑动窗口算法概述
    • 1️⃣滑动窗口算法思想
    • 2️⃣滑动窗口算法使用场景
    • 3️⃣滑动窗口算法使用思路
  • 二、滑动窗口算法代码模板
  • 三、例题1:长度最小的子数组
    • 1️⃣题目描述
    • 2️⃣思想概述
    • 3️⃣代码实现
  • 四、例题2:最小覆盖子串
    • 1️⃣题目描述
    • 2️⃣思想概述
    • 3️⃣代码实现
  • 后记

在这里插入图片描述

一、滑动窗口算法概述

1️⃣滑动窗口算法思想

滑动窗口算法是在给定特定窗口大小(当然也可以是动态可变窗口)的数组或者字符串上进行操作的算法,该算法主要的用途就是在于将嵌套循环时间复杂度的效率优化成为线性时间复杂度。简而言之,滑动窗口算法在一个特定大小的字符串或数组上进行操作,而不在整个字符串和数组上操作,这样就降低了问题的复杂度,从而也达到降低了循环的嵌套深度。

从字面意思上理解的话:

滑动: 说明这个窗口是移动的,也就是移动是按照一定方向来的。

窗口: 窗口大小并不是固定的,可以不断扩容直到满足一定的条件;也可以不断缩小,直到找到一个满足条件的最小窗口;当然也可以是固定大小。

一般来讲,滑动窗口算法需要借助两个指针left与right来完成。

2️⃣滑动窗口算法使用场景

一般可以使用滑动窗口算法的题目中会包含以下关键词:

  • 满足XXX条件(计算结果,出现次数,同时包含)
  • 最长/最短
  • 子串/子数组/子序列

例如: 长度最小的子数组

3️⃣滑动窗口算法使用思路

滑动窗口算法使用思路(寻找最长):

  • 核心: 左右双指针(L, R)在起始点,通过循环R向右逐位移动,直到R移到最后位置;
  • 每次滑动过程中:如果窗内元素满足条件,R向右移动扩大窗口,并更新最优结果;如果窗内元素不满足条件,L向右移动缩小窗口。

滑动窗口算法使用思路(寻找最短):

  • 核心: 左右双指针(L, R)在起始点,通过循环R向右逐位移动,直到R移到最后位置;
  • 每次滑动过程中:如果窗内元素不满足条件,R向右移动扩大窗口,并更新最优结果;如果窗内元素满足条件,L向右移动缩小窗口。

二、滑动窗口算法代码模板

寻找最长问题的模板:

// left,right指针分别指向窗口的左边与后边位置
// result用来保存计算的结果,该结果需要满足一定的条件
// baseResult用来保存需要求得的结果:最长的长度
初始化left,right, result, bestResult;

while(右指针没有到结尾) {
    窗口扩大,加入right对应元素,更新当前result;
    while(result不满足要求){
        窗口缩小,移除left对应元素,left右移;
    }
    更新最优结果到bestResult;
    right++;
}

return bestResult;

寻找最短问题的模板:

// left,right指针分别指向窗口的左边与后边位置
// result用来保存计算的结果,该结果需要满足一定的条件
// baseResult用来保存需要求得的结果:最短的长度
初始化left,right, result, bestResult;

while(右指针没有到结尾){
    窗口扩大,加入right对应元素,更新当前result;
    while(result满足要求){
        更新最优结果bestResult;
        窗口缩小,移除left对应元素,left右移;
    }
    right++;
}

return bestResult;

三、例题1:长度最小的子数组

1️⃣题目描述

LeetCode209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

2️⃣思想概述

  • 本题中需要满足的条件为:子数组的和 ≥ target;
  • 本题中需要求得的结果为:满足条件的子数组的最小长度。

有了这两个条件我们就可以套用寻找最短问题的模板来求解问题。

3️⃣代码实现

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int right = 0;
        int sum = 0;
        int minLength = Integer.MAX_VALUE;
        // 右指针没有到结尾
        while(right < nums.length){
            sum += nums[right];
            // sum满足条件,更新最优结果,并尝试向右移动left指针来缩小窗口
            while(sum >= target){
                minLength = Math.min(minLength, right - left + 1);
                sum -= nums[left];
                left++; 
            }
            right++;
        }
        return minLength == Integer.MAX_VALUE ? 0 : minLength;
    }
}

四、例题2:最小覆盖子串

上面的题目还是比较简单的,如果掌握了滑动窗口算法的话,下面我们来看一个困难级别的题目来体验一下。

1️⃣题目描述

LeetCode76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

提示:

m == s.length
n == t.length
1 <= m, n <= 105
s 和 t 由英文字母组成

2️⃣思想概述

  • 本题中需要满足的条件为: 字符串s的子串能够涵盖字符串 t 中的所有字符;
  • 本题中需要求得的结果为:满足条件的最小子串。

有了这两个条件,通过套用寻找最短问题的模板来求解该问题也是没太大难度的。这题的难点在于我们如何来判断字符串s的某个子串能够涵盖字符串 t 中的所有字符。

我们可以用一个哈希表来表示字符串 t 中所有的字符以及它们出现的个数,用另外一个哈希表动态维护窗口中所有的字符以及它们出现的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于 t 的哈希表中各个字符的个数,那么当前的窗口是可行的(对于该判断我们可以专门写一个方法来实现)。

3️⃣代码实现

class Solution {
    // 用来动态维护窗口中子串的所有字符以及它们出现的个数
    Map<Character,Integer> num_s = new HashMap<Character,Integer>();
    // 用来保存字符串 t 中所有的字符以及它们出现的个数
    Map<Character,Integer> num_t = new HashMap<Character,Integer>();
    public String minWindow(String s, String t) {
        // 先获取字符串 t 中所有的字符以及它们出现的个数
        for(int i = 0; i < t.length(); i++){
            char c = t.charAt(i);
            num_t.put(c, num_t.getOrDefault(c, 0) + 1);
        }
        int left = 0;
        int right = 0;
        int res_l=-1,res_r=-1;
        int minLength = Integer.MAX_VALUE;
        // 右指针没有到结尾
        while(right < s.length()){
            char c2 = s.charAt(right);
            if(num_t.containsKey(c2)){
                num_s.put(c2, num_s.getOrDefault(c2, 0) + 1);
            }
            // 判断当前的子串是否包含字符串t,满足条件的话,更新最优结果,并尝试向右移动left指针来缩小窗口
            while(check() && left <= right){
                if(right - left +1<minLength){
                    res_l = left;
                    res_r = right;
                    minLength = right - left +1;
                }
                char c3 = s.charAt(left);
                if(num_s.containsKey(c3)){
                    num_s.put(c3, num_s.getOrDefault(c3, 0) - 1);
                }
                left++;
            }
            right++;
        }
        
        if(minLength == Integer.MAX_VALUE){
            return "";
        }else{
            return s.substring(res_l, res_r+1);
        }
        // 上边这段代码的简写
        // return minLength == Integer.MAX_VALUE ? "" : s.substring(res_l, res_r+1);
        
    }

    // 用来判断某子串是否涵盖t中所有字符的方法
    public boolean check(){
        Iterator iter = num_t.entrySet().iterator();
        while(iter.hasNext()){
            Map.Entry entry = (Map.Entry) iter.next();
            Character key = (Character) entry.getKey();
            Integer val = (Integer) entry.getValue();
            if(num_s.getOrDefault(key, 0) < val){
                return false;
            }
        }
        return true;
    }

}

后记

在这里插入图片描述

本文为大家分享了 滑动窗口算法的思想,并且 为大家梳理了用滑动窗口算法解决问题的代码模板,最后通过两个经典例题(长度最小的子数组最小覆盖子串)来帮助大家更好的理解与掌握滑动窗口算法~

Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

你可能感兴趣的:(数据结构和算法,算法,leetcode,java)