滑动窗口算法专题(1)

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

个人主页:我要学编程(ಥ_ಥ)-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 < targetright++;}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 < targetright++;}// 可能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 {// 记录最长子串的长度,并更行countlen = 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) { // 要跳过0left++;}// 此时left对应的位置为0left++;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,可能后面更新的结果是0while (right < nums.length) {sum += nums[right];if (sum < target) {right++; // 继续往后寻找} else { // sum >= targetwhile (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.rhkb.cn/news/428940.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java调用数据库 笔记06 (修改篇)

1.创建Java的普通class类 2.加载驱动 Class.forName("com.mysql.jdbc.Driver"); 3.驱动管理类调用方法进行连接&#xff0c;得到连接对象 DriverManager.getConnection(url, user, password); 其中设置参数&#xff1a; static final String url "jdbc:my…

python中ocr图片文字识别样例(二)

一、说明 本次解决图片相关出现中文乱码问题&#xff0c;属于上篇文章的优化&#xff0c;前提条件依赖上篇文章的包&#xff0c;当然ocr的具体应用场景很多&#xff0c;根据自身需求进行调整 二、具体实现 2.1 代码实现&#xff1a; # -*- coding: utf-8 -*- import easyoc…

电气设备施工现场风险状态判断ai模型训练数据集

电气设备施工现场风险状态判断ai模型训练数据集 id:18 电气设备施工现场工人人工智能学习数据和工作环境安全数据&#xff0c;建立系统化管理体系&#xff0c;改变全球EHS范式&#xff0c;预防工业事故。数据集记录了387709例子电力设施建设以及施工现场相关的灾害安全环境数据…

电力行业螺钉螺帽螺丝缺失检测数据集 voc yol

电力行业螺钉螺帽螺丝缺失检测数据集 数据集描述 该数据集旨在用于电力行业中的螺钉、螺帽、螺丝等紧固件的缺失检测任务。数据集包含了大量的图像及其对应的标注信息&#xff0c;可用于训练计算机视觉模型&#xff0c;以识别和定位电力设施中的螺钉、螺帽、螺丝等部件是否存在…

【零成本】七日杀 服务器搭建 异地联机 无需公网IP、服务器

主要内容 什么是七日杀 搭建前需要准备什么 详细步骤 1.Steam中下载七日杀服务器工具 2.修改七日杀服务配置文件 3.启动七日杀服务器应用 4.运行 MoleSDN 进行异地联机 5.小伙伴打开游戏加入 鼠鼠的服务器 什么是七日杀 《七日杀》是一款沙盒生存恐怖游戏&#xff0c;…

【2025】儿童疫苗接种预约小程序(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

LeetCode[中等] 54.螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 思路&#xff1a;定义方向数组&#xff0c;按照顺时针顺序&#xff1a;右(0,1)&#xff0c;下(1,0)&#xff0c;左(0,-1)&#xff0c;上(0,-1) 从矩阵的左上角开始遍历…

5. 数字证书与公钥基础设施

5. 数字证书与公钥基础设施 (1) PKI 的定义、组成及应用 PKI(Public Key Infrastructure,公钥基础设施) 是一个使用公钥技术来提供安全服务的框架。它定义了如何管理和维护公钥,以及如何通过证书来验证公钥的真实性。PKI的核心组成部分包括: 证书颁发机构(CA, Certifica…

Maven 安装

Maven 安装 Maven 下载安装 下载MAVEN 选择版本注意&#xff1a; IDEA 2022 兼容maven 3.8.1及之前的所用版本 IDEA 2021 兼容maven 3.8.1及之前的所用版本 IDEA 2020 兼容Maven 3.6.3及之前所有版本 IDEA 2018 兼容Maven3.6.1及之前所有版本 打开MAVEN官网 下载需要的版本 Wi…

软件设计师——操作系统

&#x1f4d4;个人主页&#x1f4da;&#xff1a;秋邱-CSDN博客☀️专属专栏✨&#xff1a;软考——软件设计师&#x1f3c5;往期回顾&#x1f3c6;&#xff1a;C: 类和对象&#xff08;上&#xff09;&#x1f31f;其他专栏&#x1f31f;&#xff1a;C语言_秋邱 一、操作系统…

Qt_窗口界面QMainWindow的介绍

目录 1、菜单栏QMenuBar 1.1 使用QMainWindow的准备工作 1.2 在ui文件中设计窗口 1.3 在代码中设计窗口 1.4 实现点击菜单项的反馈 1.5 菜单中设置快捷键 1.6 菜单中添加子菜单 1.7 菜单项中添加分割线和图标 1.8 关于菜单栏创建方式的讨论 2、工具栏QToolBar …

谷歌-BERT-“bert-base-chinese ”

1 需求 需求&#xff1a;自动下载模型和分词器 需求&#xff1a;手动导入模型和分词器 需求&#xff1a;pipeline使用预训练模型 需求&#xff1a;训练和评估 需求&#xff1a;测试 关键词&#xff1a;训练数据集、评估数据集、测试数据集 需求&#xff1a;上线 2 接口 3 自…

[UTCTF2020]sstv

用goldwave和010editor打开均未发现线索&#xff0c; 网上搜索sstv&#xff0c;豆包回答如下&#xff1a; 慢扫描电视&#xff08;Slow Scan Television&#xff0c;简称 SSTV&#xff09;是一种通过无线电传输和接收静态图像的技术。 一、工作原理 SSTV 通过将图像逐行扫描并…

鸿蒙OpenHarmony【轻量系统内核通信机制(互斥锁)】子系统开发

互斥锁 基本概念 互斥锁又称互斥型信号量&#xff0c;是一种特殊的二值性信号量&#xff0c;用于实现对共享资源的独占式处理。 任意时刻互斥锁的状态只有两种&#xff0c;开锁或闭锁。当任务持有互斥锁时&#xff0c;该互斥锁处于闭锁状态&#xff0c;这个任务获得该互斥锁…

利用Metasploit进行信息收集与扫描

Metasploit之信息收集和扫描 在本文中&#xff0c;我们将学习以下内容 使用Metasploit被动收集信息 使用Metasploit主动收集信息 使用Nmap进行端口扫描 使用db_nmap方式进行端口扫描 使用ARP进行主机发现 UDP服务探测 SMB扫描和枚举 SSH版本扫描 FTP扫描 SMTP枚举 …

基于python上门维修预约服务数据分析系统

目录 技术栈和环境说明解决的思路具体实现截图python语言框架介绍技术路线性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示操作可行性详细视频演示源码获取 技术栈和环境说明 结合用户的使用需求&#xff0c;本系统采用运用较为广…

Git使用详解:从安装到精通

前言 什么是Git Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件&#xff08;Java类、xml文件、html页面等&#xff09;&#xff0c;在软件开发过程中被广泛使用。 可以理解&#xff1a; git是一个管理源代码的工具&#xff0c;主要用于企业团队开…

接口自动化框架入门(requests+pytest)

一、接口自动化概述 二、数据库概述 2.1 概念 存储数据的仓库&#xff0c;程序中数据的载体 2.2 分类 关系型数据库&#xff1a;安全 如mysql&#xff0c;oracle&#xff0c;SQLLite database tables 行列 非关系型数据库&#xff1a;高效 如redis&#xff0c;mongoDB 数…

学习大数据DAY59 全量抽取和增量抽取实战

目录 需求流程&#xff1a; 需求分析与规范 作业 作业2 需求流程&#xff1a; 全量抽取 增量抽取 - DataX Kettle Sqoop ... 场景: 业务部门同事或者甲方的工作人员给我们的部门经理和你提出了新的需 求 流程: 联系 > 开会讨论 > 确认需求 > 落地 需求文档( 具体…

4.提升客户服务体验:ChatGPT在客服中的应用(4/10)

本文大纲旨在指导撰写一篇全面探讨ChatGPT如何通过优化客户服务流程、提供实际应用案例和用户反馈&#xff0c;以提升客户服务体验的深入博客文章。 引言 在当今竞争激烈的商业环境中&#xff0c;客户服务已成为企业成功的关键因素。优质的客户服务不仅能够增强客户满意度和忠…