滑动窗口算法专题(1)

news2025/2/23 5:35:42

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏: 优选算法专题

目录

滑动窗口算法的简介

209. 长度最小的子数组

3.无重复字符的最长子串

1004. 最大连续1的个数III

1658. 将×减到0的最小操作数


滑动窗口算法的简介

滑动窗口算法本质上就是双指针算法的一个衍生版本。

这个就是类似双指针算法里的快慢指针。但是快慢指针一般用去求序列的长度或是中间位置等问题,且快慢指针的速度一般都是固定的。但是滑动窗口算法中的 left指针和 right指针移动是根据题目的要求进行的。

滑动窗口算法的步骤也是固定的,即有自己固定的套路。一般步骤如下:

1、初始化窗口:left = 0,right = 0;

2、 扩大窗口:right++;

3、判断题目的要求。

接下来就是根据 第三步的结果来进行不同的处理。

如果满足题目要求,就继续扩大窗口的大小,再进行判断。即重复2、3步骤。如果不满足题目要求就得更新结果,再缩小窗口,接着再去判断是否满足题目要求,即循环判断是否满足题目要求。

滑动窗口算法广泛应用于数组和字符串等序列中求子序列的问题。例如:求子数组、子字符串等。

上面就是简介的全部了。

下面就开始实战。

209. 长度最小的子数组

题目: 

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

思路: 首先想到的就是去遍历数组,找出不同的长度,然后比较找出最小值即可。

最暴力的解法就是枚举每一个数组元素的对应长度的值,最终最短的路径一定是在这些值里面的。

代码实现:

错误解法(暴力枚举):

class Solution {
    // 暴力枚举
    public int minSubArrayLen(int target, int[] nums) {
        int sum = 0;
        int right = 0;
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        int left = 0;
        while (right < nums.length) {
            // 首先得计算sum的值
            sum += nums[right];
            // 再判断sum 和 target的大小关系
            if (sum < target) {
                right++;
            } else {
                // 将长度,入堆,再继续往后走
                minHeap.add(right-left+1);
                right = ++left;
                sum = 0;
            }
        }
        // 小根堆可能为空,得判断
        return minHeap.isEmpty() == true ? 0 : minHeap.peek();
    }
}

上面代码去运行时,会超出时间限制。但是有一个让我很疑惑的地方。如下所示:

我个人认为是这个后台在跑一个测试用例时,能通过;但是全部一起跑的时候,最终的叠加时间超出了时间限制。 

接下来就得开始进行优化。上面的代码有一个地方时发生了重复计算的:当我们找出符合条件的子区间时,不应该从头继续开始,我们上面的做法是跳过left所在的元素,继续往后遍历寻找。但是有一个更优的方法:直接让left++不就行了吗。这就节约了继续跑的时间。

但有小伙伴可能会疑惑:有没有可能漏掉了值呢?答案是不可能的。现在right所在的位置是上一次满足条件的位置,并且这个位置是加上left才满足的,因此去掉left之后,在[left+1,right)的位置肯定不会满足条件的。因此我们可以大胆的去做。

但是可能在去掉 left 之后,还可能继续大于 target,因此这里我们可以加上一个循环判断直至小于target。

优化解法: 

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int right = 0;
        int sum = 0;
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        while (right < nums.length) {
            sum += nums[right];
            // 满足条件,就一直更新并缩小窗口
            while (sum >= target) {
                minHeap.add(right-left+1);
                sum -= nums[left++];
            }
            // 走到这里说明sum < target
            right++;
        }
        return minHeap.isEmpty() ? 0 : minHeap.peek();
    }
}

上面的代码的效率还不是最优,因为我们使用堆这个数据结构来存储和计算最小长度。这个时间可不能忽略。因此下面就是不使用堆的做法。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int right = 0;
        int sum = 0;
        // 这里不能是0,求最小值时会有影响
        int len = Integer.MAX_VALUE;
        while (right < nums.length) {
            sum += nums[right];
            // 满足条件,就一直更新并缩小窗口
            while (sum >= target) {
                len = Math.min(len, right-left+1);
                sum -= nums[left++];
            }
            // 走到这里说明sum < target
            right++;
        }
        // 可能while循环没进入,len的值还是int最大值
        return len == Integer.MAX_VALUE ? 0 : len;
    }
}

下面就来分析一下,滑动窗口的做法:

3.无重复字符的最长子串

题目:

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串(可以去原题看解释) 的长度。

示例 1:

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

示例 2:

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

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

思路: 这里要我们找不重复的最长子串。首先看到不重复,我们就应该想到用哈希表来解决。那么这里的暴力枚举思路也就出来了。直接遍历字符串,找到一个字符,看其是否在哈希表中。如果在,则从下一个位置开始遍历;反之,则将其加入哈希表,继续往后遍历。

代码实现: 

暴力枚举版本(能通过,但效率低下):

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left = 0;
        int right = 0;
        Map<Character, Integer> hash = new HashMap<>();
        int count = 0;
        int len = 0;
        while (right < s.length()) {
            char ch = s.charAt(right);
            // 看哈希表中是否出现过这个元素
            if (hash.getOrDefault(ch, 0) == 0) {
                // 没出现就添加,再继续遍历
                hash.put(ch, 1);
                right++;
                count++;
            } else {
                // 记录最长子串的长度,并更行count
                len = Math.max(count, len);
                count = 0;
                // right从头开始,哈希表中的元素也得清除
                right = ++left;
                hash.clear();
            }
        }
        // 可能出现没有重复的现象或者未被更新
        len = Math.max(count, len);
        return len;
    }
}

 这个代码的思想总体上和上一题的暴力枚举是一样的。出现了重复的字符,就从left+1的位置继续开始遍历。上一题在处理这种情况时,是我们了解了在right位置之前不会出现这种情况,所以我们可以直接让left++。但是在这里即使left++了也不一定能跳过重复的字符。

优化后的版本: 

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left = 0;
        int right = 0;
        Map<Character, Integer> hash = new HashMap<>();
        int count = 0;
        int len = 0;
        while (right < s.length()) {
            char ch = s.charAt(right);
            // 看哈希表中是否出现过这个元素
            if (hash.getOrDefault(ch, 0) == 0) {
                // 没出现就添加,再继续遍历
                hash.put(ch, 1);
                right++;
                count++;
            } else {
                // 判断是否还有重复的元素
                while (hash.getOrDefault(ch, 0) != 0) {
                    len = Math.max(len, count); // 更新长度
                    hash.remove(ch); // 删除元素(不一定是重复的)
                    count--; // 计数器--
                    ch = s.charAt(left++); // 遍历找寻重复的元素
                }
            }
        }
        // 同样也可能全部是不重复的
        len = Math.max(len, count);
        return len;
    }
}

 但这个还不是最优的版本。由于我们使用了哈希表这个数据结构,在查询和删除时,都需要浪费一些时间。因此最优的版本就是使用数组来模拟哈希表。

最优版本:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left = 0;
        int right = 0;
        int[] hash = new int[128]; // 根据ASCII码表来指定
        int count = 0;
        int len = 0;
        while (right < s.length()) {
            char ch = s.charAt(right);
            // 看哈希表中是否出现过这个元素
            if (hash[ch] == 0) {
                // 没出现就添加,再继续遍历
                hash[ch]++;
                right++;
                count++;
            } else {
                // 判断是否还有重复的元素
                while (hash[ch] != 0) {
                    len = Math.max(len, count); // 更新长度
                    hash[ch]--; // 删除元素(不一定是重复的)
                    count--; // 计数器--
                    ch = s.charAt(left++); // 遍历找寻重复的元素
                }
            }
        }
        // 同样也可能全部是不重复的
        len = Math.max(len, count);
        return len;
    }
}

这个最优版本就是用数组模拟实现了哈希表而已,并没有其他的多余变化。 

从这里我们也可以得出一个结论:能够自己手动的模拟实现该数据结构的功能,尽量可以手动实现,这不仅可以提升时间效率,而且在面试的时候,还能让面试官对我们另眼相看。

1004. 最大连续1的个数III

题目:

给定一个二进制数组 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。

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1
  • 0 <= k <= nums.length

注意:这个题目一定不能去修改对应位置为1,那样根本就解不出来。此次修改对应位置的值之后,拿下一次还得还原并且将另外的位置进行修改。因此这种修改对应位置的题目,一般是采用一个计数器来进行逻辑修改,从而实现最终的解题。 

思路:题目的要求就是让我们找到数组中连续1的最大长度。即在序列中寻找符合要求的子序列。这就是用滑动窗口算法来解决。首先考虑暴力枚举的方法。从 left 为0的位置开始往后遍历,遇到1,right++,遇到0,计数器++。当计数器的长度等于k时,就得停下来了,计算此时序列的长度。然后再从left+1的位置开始继续遍历,直至序列遍历完成。接下来就要去优化了。如下所示:

代码实现:

这里就不再给出暴力枚举的代码,大家可以自行实现(参照上面的暴力枚举):

class Solution {
    public int longestOnes(int[] nums, int k) {
        int left = 0;
        int right = 0;
        int len = 0;
        int count = 0;
        while (right < nums.length) {
            if (nums[right] == 1) { // 继续走
                right++;
            } else { // 判断0的个数是否符合要求
                if (count < k) {
                    count++;
                    right++; // 还能继续走
                } else {
                    // 此时right的位置并非合法位置
                    len = Math.max(len, right-left);
                    while (nums[left] == 1) { // 要跳过0
                        left++;
                    }
                    // 此时left对应的位置为0
                    left++;
                    count--;
                }
            }
        }
        // 可能会出现没有统计len的情况,即count<k一直满足
        len = Math.max(len, right-left);
        return len;
    }
}

1658. 将×减到0的最小操作数

题目:

给你一个整数数组 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 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109

思路:读完题,如果从正面直接去写,根本没有丝毫的思路。从上面给的示例也可以看出:左边突然使用一下,接着又是右边突然使用一下,这根本没有任何的规律和逻辑。因此我们得从反面来写,题目是让我们找出长度最小的一段或者多段连续的序列的和刚好等于x(序列必须来自最左边或者最右边)。我们完全可以按照下面的方式来处理:

因此,这里题目就转换成了寻找一段连续的最长的序列其和的值为sum-x即可。这里就是用到滑动窗口的算法步骤来解决。

代码实现:

class Solution {
    public int minOperations(int[] nums, int x) {
        // 计算出要寻找的目标值
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        int target = sum - x;
        // 当target小于0时,肯定不可能找到(数组元素全部是大于0的)
        if (target < 0) {
            return -1;
        }
        // 找出最长的序列长度
        int left = 0;
        int right = 0;
        sum = 0;
        int len = -1; // 这里不能是0,可能后面更新的结果是0
        while (right < nums.length) {
            sum += nums[right];
            if (sum < target) {
                right++; // 继续往后寻找
            } else { // sum >= target
                while (sum > target) {
                    sum -= nums[left];
                    left++;
                }
                if (sum == target) {
                    len = Math.max(len, right-left+1); // 记录此时的长度
                }
                // 这里避免死循环造成left越界
                right++;
            }
        }
        // 要么计算出来了值,要么啥也没有
        return len == -1 ? -1 : nums.length-len; 
    }
}

注意:这里len的初始化一定不能是0。因为当len是0,并且target == sum 时,最后就会导致left与right一直是指向同一个位置,即两者算出的长度为0,但是我们最后再判断时,输出的结果是-1;而我们想要的结果是 nums.length。因此这里一定不能初始化为0。

总结:这一题算是这四个题目中最难的一个。思路就不是正常人可以想到的,即使想到了之后,后面的一些细节问题的处理也是需要我们正常人去反复调试对比的,根本不是一次就能AC的。这道题难度还是非常大的。 

好啦!本期 滑动窗口算法专题(1)的学习之旅 就到此结束啦!我们下一期再一起学习吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2153612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker基本使用(持续更新中)

docker介绍 docker是一个开源的应用容器引擎&#xff0c;常见的容器引擎例如&#xff1a;docker、podman、containerd等&#xff0c;但是docker应用还是相对比较多。 1 常用命令 1.1保存镜像到本地 命令如下: docker save -o nginx.tar nginx:latest 举例 结果&#xff1a…

C++/Qt 集成 AutoHotkey

C/Qt 集成 AutoHotkey 前言AutoHotkey 介绍 方案一&#xff1a;子进程启动编写AutoHotkey脚本准备 AutoHotkey 运行环境编写 C/Qt 代码 方案二&#xff1a;显式动态链接方案探索编译动态链接库集成到C工程关于AutoHotkeyDll.dll中的函数原型 总结 前言 上一篇介绍了AutoHotkey…

求1000以内所有恰好能分解成10组两个素数之和

要求 根据哥德巴赫猜想&#xff0c;任意一个大偶数都可以分解为两个素数之和。但许多偶数分解为两个素数之和并不是唯一的。 请编写函数fun&#xff0c;其功能是:求1000(不包括1000)以内的所有恰好能分解成10组两个素数之和(5109和1095被认为是同一组)的偶并依次存入数组a中并…

SQL_yog安装和使用演示--mysql三层结构

目录 1.什么是SQL_yog 2.下载安装 3.页面介绍 3.1链接主机 3.2创建数据库 3.3建表操作 3.4向表里面填内容 3.5使用指令查看效果 4.连接mysql的指令 4.1前提条件 4.2链接指令 ​编辑 4.3创建时的说明 4.4查看是不是连接成功 5.mysql的三层结构 1.什么是SQL_yog 我…

有什么兼容macOS 15 Sequoia系统的加密软件?

前言&#xff1a;近日&#xff0c;苹果更新了 macOS 15 Sequoia正式版&#xff0c;已经有用户在电脑上安装使用了。在这个信息化时代&#xff0c;系统一直在更新&#xff0c;运用一些工具时需要考虑兼容性。 刚有个客户来问迅软&#xff1a;你们迅软DSE客户端支持新发布的macO…

python函数三:拆包和交换变量值、引用、匿名函数

文章目录 1. 拆包和交换变量值1.1 拆包1.2 交换变量值 2. 引用2.1 了解引用2.1 把引用当作参数传递 3. 匿名函数3.1 lambda语法3.2 lambda的应用3.3 使用使用函数形式来求解某天是该年的第几天&#xff1f; 1. 拆包和交换变量值 1.1 拆包 拆包&#xff1a;把组合形成的元组形…

Linux基础---13三剑客及正则表达式

一.划水阶段 首先我们先来一个三剑客与正则表达式混合使用的简单示例&#xff0c;大致了解是个啥玩意儿。下面我来演示一下如何查询登录失败的ip地址及次数。 1.首先&#xff0c;进入到 /var/log目录下 cd /var/log效果如下 2.最后&#xff0c;输入如下指令即可查看&#xf…

【读书笔记-《网络是怎样连接的》- 0】全书整体结构

网络是计算机相关课程中的重要部分&#xff0c;更是当今的学习生活中所不可或缺的。虽然相关的经典书籍很多&#xff0c;但是大多数属于深入某一部分的专著&#xff0c;比如TCP/IP协议。像我这样对于网络一知半解的同学来说&#xff0c;更需要一种覆盖网络全貌&#xff0c;每一…

《线性代数》笔记

文章目录 1 行列式1.1 克拉默法则1.2 基本性质1.3 余子式 M i j M_{ij} Mij​1.4 代数余子式 A i j ( − 1 ) i j ⋅ M i j A_{ij} (-1)^{ij} \cdot M_{ij} Aij​(−1)ij⋅Mij​1.5 具体型行列式计算&#xff08;化为基本型&#xff09;1.5.1 主对角线行列式&#xff1a;主…

Python学习——【4.2】数据容器:tuple元组

文章目录 【4.2】数据容器&#xff1a;tuple元组一、元组的定义格式二、元组的特点三、元组的操作&#xff08;一&#xff09;常见操作&#xff08;二&#xff09;循环遍历 【4.2】数据容器&#xff1a;tuple元组 一、元组的定义格式 为什么需要元组 列表是可以修改的。如果想…

seL4 Untyped(二)

链接: Untyped Untyped 这篇主要是针对seL4物理内存管理的介绍。 物理内存 在seL4系统中&#xff0c;除了内核占用的一小部分静态内存之外&#xff0c;其他的所有的物理内存都是用户一级管理的。seL4在启动时创建的对象能力&#xff0c;以及seL4管理的其余物理资源&#xf…

初始网络编程(下)

所属专栏&#xff1a;Java学习 1. TCP 的简单示例 同时&#xff0c;由于 TCP 是面向字节流的传输&#xff0c;所以说传输的基本单位是字节&#xff0c;接受发送都是使用的字节流 方法签名 方法说明 Socket accept() 开始监听指定端口&#xff08;创建时绑定的端口&…

十七、RC振荡电路

振荡电路 1、振荡电路的组成、作用、起振的相位条件以及振荡电路起振和平衡幅度条件&#xff0c; 2、RC电路阻抗与频率、相位与频率的关系曲线; 3、RC振荡电路的相位条件分析和振荡频率

信息安全数学基础(15)欧拉定理

前言 欧拉定理是数论中的一个重要定理&#xff0c;它建立了模运算下指数与模的互质关系。这个定理在密码学、信息安全等领域有着广泛的应用&#xff0c;特别是在公钥密码体制&#xff08;如RSA加密算法&#xff09;中。 一、表述 设 n 是一个正整数&#xff0c;a 是一个与 n 互…

Tomcat服务器—Windows下载配置详细教程

一、关于 1.1 简介 Tomcat是一个开源的Java Servlet容器和Web服务器&#xff0c;由Apache软件基金会维护。它实现了Java Servlet和JavaServer Pages (JSP) 规范&#xff0c;用于运行Java Web应用程序。Tomcat支持多种Java EE功能&#xff0c;并提供了高效的性能和可扩展性&am…

Spring扩展点系列-MergedBeanDefinitionPostProcessor

文章目录 简介源码分析示例示例一&#xff1a;Spring中Autowire注解的依赖注入 简介 spring容器中Bean的生命周期内所有可扩展的点的调用顺序 扩展接口 实现接口ApplicationContextlnitializer initialize AbstractApplicationContext refreshe BeanDefinitionRegistryPos…

记录一个英语听力网站的开发

背景 在当前全球经济衰退的背景下&#xff0c;国内IT相关工作的竞争日益激烈。为了获得更多的职业机会&#xff0c;学习英语或许能为程序员打开一扇新的窗户。尤其是在国际化背景的远程工作中&#xff0c;英语协作沟通是必不可少的。 尽管我们大多数人从小到大都在学习英语&a…

使用Renesas R7FA8D1BH (Cortex®-M85)和微信小程序App数据传输

目录 概述 1 系统架构 1.1 系统结构 1.2 系统硬件框架结构 1.3 蓝牙模块介绍 2 微信小程序实现 2.1 UI介绍 2.2 代码实现 3 上位机功能实现 3.1 通信协议 3.2 系统测试 4 下位机功能实现 4.1 功能介绍 4.2 代码实现 4.3 源代码文件 5 测试 5.1 编译和下载代码…

RNN的反向传播

目录 1.RNN网络&#xff1a;通过时间反向传播(through time back propagate TTBP) 2.RNN梯度分析 2.1隐藏状态和输出 2.2正向传播&#xff1a; 2.3反向传播&#xff1a; 2.4问题瓶颈&#xff1a; 3.截断时间步分类&#xff1a; 4.截断策略比较 5.反向传播的细节 ​编辑…

大数据新视界 --大数据大厂之JavaScript在大数据前端展示中的精彩应用

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…