[算法] 优先算法(三):滑动窗口(上)

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343

🏵️热门专栏:🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482

🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482

🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482

🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482

感谢点赞与关注~~~

目录

  • [1. 概述](#1. 概述)
  • [2. 长度最小的子数组(难度:🔵2度)](#2. 长度最小的子数组(难度:🔵2度))
  • [3. 无重复字符的最长子串(难度:🔵2度)](#3. 无重复字符的最长子串(难度:🔵2度))
  • [4. 最大连续1的个数III(难度:🟡3度)](#4. 最大连续1的个数III(难度:🟡3度))
  • [5. 将x减到0的最小操作数(难度:🟡3度)](#5. 将x减到0的最小操作数(难度:🟡3度))

1. 概述

所谓滑动窗口,也叫通向双指针 ,就是在我们上一个板块双指针的基础上,把双指针的"点"变换成"线",双指针表示两个点,而滑动窗口则是由双指针的两个"点"构成"线",表示一个区间.

滑动窗口最基本有以下几步的操作:

  1. 指定left=0right=0两个左右指针.
  2. 让右指针右移,进入窗口.
  3. 让左指针右移,出窗口.
  4. 更新结果,结果在哪一步更新不确定,需要具体问题具体分析.

2. 长度最小的子数组(难度:🔵2度)

OJ链接

  • 题目描述

给定一个含有 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

  • 算法原理
    由于此问题分析的对象是「⼀段连续的区间」,因此可以考虑「滑动窗⼝」的思想来解决这道题。
    让滑动窗⼝满⾜:从i 位置开始,窗⼝内所有元素的和⼩于target (那么当窗⼝内元素之和
    第⼀次⼤于等于⽬标值的时候,就是i 位置开始,满⾜条件的最⼩⻓度)。
    做法:将右端元素划⼊窗⼝中,统计出此时窗⼝内元素的和:
    • 如果窗⼝内元素之和⼤于等于target :更新结果,并且将左端元素划出去的同时继续判
      断是否满⾜条件并更新结果
      (因为左端元素可能很⼩,划出去之后依旧满⾜条件)
    • 如果窗⼝内元素之和不满⾜条件: right++ ,另下⼀个元素进⼊窗⼝。
  • 为什么滑动窗口可以保证最终结果的正确性,而且时间复杂度很低?
    • 这个窗⼝寻找的是:以当前窗口最左侧元素(记为left1 )为基准 ,符合条件的情况。也
      就是在这道题中,从left1 开始,满⾜区间和sum >= target 时的最右侧(记为right1 )能到哪⾥。
    • 我们既然已经找到从left1 开始的最优的区间,那么就可以大胆舍去left1 。但是如
      果使用暴力解法,重新开始统计第⼆个元素( left2 )往后的和,势必会有⼤量重复
      的计算(因为我们在求第⼀段区间的时候,已经算出很多元素的和了,这些和是可以在计算
      下次区间和的时候用上的)。
    • 此时, rigth1 的作⽤就体现出来了,我们只需将left1 这个值从sum 中剔除 。从
      right1 这个元素开始,往后找满足left2 元素的区间(此时right1 也有可能是满
      ⾜的,因为left1 可能很⼩。 sum 剔除掉left1 之后,依旧满⾜⼤于等于
      target )。这样我们就能省掉⼤量重复的计算。
    • 这样我们不仅能解决问题,⽽且效率也会⼤⼤提升。
      时间复杂度:虽然代码是两层循环,但是我们的left 指针和right 指针都是不回退的,两者
      最多都往后移动n 次。因此时间复杂度是O(N) 。
    • 最后需要注意的一点就是,在一开始定义len的时候,由于题目中让我们找的是最小值,所以我们在给len赋值的时候,要赋值的是Integer的最大值.如果赋值为0,结果会一直是0.
  • 代码编写
java 复制代码
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int right = 0;
        int len = Integer.MAX_VALUE;//这里之所以要定义整数的最大值,是因为后面计算最小值的
        //时候,如果赋值为0,就一直是0
        int sum = 0;
        for (;right < nums.length;right++){
            sum += nums[right];
            while (sum >= target){
                len = Math.min(len,right - left + 1);//在刚好到达最小长度的时候,更新长度
                //之后出窗口的时候一直更新,直到不满足循环条件
                sum -= nums[left];
                left++;
            }
        }
        return len == Integer.MAX_VALUE? 0:len;
    }
}

3. 无重复字符的最长子串(难度:🔵2度)

OJ链接

  • 题目描述

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

的长度。

示例 1:

输入: s = "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"

输出: 1

解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"

输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

  • 算法原理
    研究对象依然是一段区间,所以我们考虑使用滑动窗口.
    1. 定义Set来统计子串中都有哪些字符.
    2. 每有一个字符进入窗口,就判断个字符在不在Set中.
    3. 如果在,就让通过移动left指针让前面的元素出窗口,每出一个,就从Set中删除一个字符,直到Set中没有right指针指向的这个字符为止.
    4. 如果不在,就进入窗口,让Set中也增加这个字符.
  • 代码编写
java 复制代码
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Set<Character> set = new HashSet<>();//定义Set,用来统计区间内的字符
        int left = 0;
        int right = 0;
        int len = 0;
        for (;right < s.length();right++){
            while (set.contains(s.charAt(right))){//判断元素是否在区间中出现过
                set.remove(s.charAt(left));//每有元素出窗口,就从Set中删除
                left++;
            }
            set.add(s.charAt(right));//每有元素进入窗口,就加入Set
            len = Math.max(len,right-left+1);
        }
        return len;
    }
}

4. 最大连续1的个数III(难度:🟡3度)

OJ链接

  • 题目描述

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2

输出:6

解释:[1,1,1,0,0,1,1,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 6。

示例 2:

输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3

输出:10

解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]

粗体数字从 0 翻转到 1,最长的子数组长度为 10。

  • 算法原理
    • 这道题不要去想怎么把0转化为1
    • 因此,我们可以把问题转化成:求数组中⼀段最⻓的连续区间,要求这段区间内0 的个数不超过k 个。既然是连续区间,可以考虑使⽤「滑动窗口」来解决问题。
    • 让数组中的元素进入窗口,每进入一次窗口,就让表示最大长度的len++ ,如果进入的元素是0,那么就让统计0个数的zero++ ,当zero的个数大于了规定个数,就让区间之前的元素出窗口,直到0恢复正常个数.
  • 代码编写
java 复制代码
class Solution {
    public int longestOnes(int[] nums, int k) {
        int left = 0;
        int right = 0;
        int len = 0;
        int zero = 0;
        for (;right < nums.length;right++){
            if (nums[right] == 0){
                zero++;
            }
            while (zero > k){
                if (nums[left] == 0){
                    zero--;
                }
                left++;
            }
            len = Math.max(len,right-left+1);
        }
        return len;
    }
}

5. 将x减到0的最小操作数(难度:🟡3度)

OJ链接

  • 题目描述

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入:nums = [1,1,4,2,3], x = 5

输出:2

解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4

输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10

输出:5

解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

  • 算法原理
    • 这道题最不好想的一点就是:这里的区间是数组两边的两个不连续的短区间,那么我们就有一种解决问题的办法正难则反.我们可以考虑把两个短区间通过使用数组长度减去,变成一个长区间 .转化成求数组内⼀段连续的、和为 sum(nums) - x 的最长数组。此时,就是熟悉的「滑动窗⼝」问题了。
    • a. 转化问题:求target = sum(nums) - x 。如果target < 0 ,问题⽆解;也就是说,本用例即使把数组中所有的数字全部用上了,也达不到要求的那个值 .
      b. 初始化左右指针l = 0 ,r = 0 (滑动窗⼝区间表⽰为[l, r) ,左右区间是否开闭很重要,必须设定与代码⼀致),记录当前滑动窗⼝内数组和的变量sum = 0 ,记录当前满⾜条件数组的最⼤区间⻓度maxLen = -1 ;
      c. 当 r ⼩于等于数组⻓度时,⼀直循环:i. 如果sum < target ,右移右指针,直⾄变量和⼤于等于target ,或右指针已经移到头;
      ii. 如果sum > target ,右移左指针,直⾄变量和⼩于等于target ,或左指针已经移到
      头;
      iii. 如果经过前两步的左右移动使得sum == target ,修改满足条件数组的最大长度,并
      让下个元素进入窗口

      d. 循环结束后,如果maxLen 的值有意义,则计算结果返回;否则,返回 -1 。
  • 代码编写
java 复制代码
class Solution {
    public int minOperations(int[] nums, int x) {
        int left = 0;
        int right = 0;
        int sum = 0;
        int sum1 = 0;
        int len = -1;
        for (int num:nums){
            sum += num;
        }
        int target = sum - x;
        if (target < 0){//如果数组的总和都不够达到x的,那么就不存在这样的数字
        //直接返回-1
            return -1;
        }
        for (;right < nums.length;right++){
            sum1 += nums[right];
            while (sum1 > target){
                sum1 -= nums[left];
                left++;
            }
            if (sum1 == target){
                len = Math.max(len,right-left+1);
            }
        }
        if (len == -1){
            return len;
        }else{
            return nums.length-len; 
        }
        
    }
}
相关推荐
一丝晨光1 分钟前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
罗曼蒂克在消亡5 分钟前
2.3MyBatis——插件机制
java·mybatis·源码学习
_GR17 分钟前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
ROBIN__dyc29 分钟前
表达式
算法
无限大.30 分钟前
c语言200例 067
java·c语言·开发语言
余炜yw31 分钟前
【Java序列化器】Java 中常用序列化器的探索与实践
java·开发语言
攸攸太上31 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
无限大.32 分钟前
c语言实例
c语言·数据结构·算法
Kenny.志34 分钟前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
六点半88836 分钟前
【C++】速通涉及 “vector” 的经典OJ编程题
开发语言·c++·算法·青少年编程·推荐算法