滑动窗口习题篇(上)

滑动窗口习题篇(上)

引言:

  • 什么是滑动窗口——同向双指针

  • 什么时候用滑动窗口——利用单调性

  • 滑动窗口的正确性——利用单调性,规避了很多没有必要的枚举行为

  • 滑动窗口的时间复杂度——O(N)

  • 用滑动窗口如何书写代码,下列例题示范

1.长度最小的子数组

题目描述:

给定一个含有 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.从前往后枚举数组中的任意一个元素,把它当成起始位置。

2.然后从这个起始位置开始,然后寻找一段最短的区间,使得这段区间的和大于等于目标值。

3.在所有元素作为起始位置所得的结果中,找到最小区间值即可。

代码实现:
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {// 记录结果int ret = INT_MAX;int n = nums.size();// 枚举出所有满⾜和⼤于等于 target 的⼦数组[start, end]// 由于是取到最⼩,因此枚举的过程中要尽量让数组的⻓度最⼩// 枚举开始位置for (int start = 0; start < n; start++){int sum = 0; // 记录从这个位置开始的连续数组的和// 寻找结束位置for (int end = start; end < n; end++){sum += nums[end]; // 将当前位置加上if (sum >= target) // 当这段区间内的和满⾜条件时{// 更新结果,start 开头的最短区间已经找到ret = min(ret, end - start + 1);break;}}}// 返回最后结果return ret == INT_MAX ? 0 : ret;}
};    

解法二:利用单调性,使用“同向双指针”来优化

“同向双指针”又称为滑动窗口

算法思路:

由于此问题分析的对象是一段连续的区间,因此可以考虑滑动窗口的思想来解决这道题。

1.进窗口判断条件:

从 i 位置开始,窗口内所有元素的和小于 target ;

滑动窗口做法:

  1. left=0,right=0;

  2. 将右端元素划入窗口中,统计出此时窗口内元素的和(记为sum):

  • 如果sum<target: right++ ,使下一个元素进入窗口。(进窗口

  • 如果sum>= target :更新结果,并且将左端元素划出去的同时继续判断是否满足条件并更新结果(因为左端元素可能很小,划出去之后依旧满足条件)(出窗口

为何滑动窗可以解决问题,并且时间复杂度更低?

1.这个窗口寻找的是:从 left开始,满足 sum >= target 时,right能到哪里

2.当right找到时,即找到了从 left开始的最优的区间,那么就可以大胆舍去当前 left ,进行left++。之后不必从++后的left进行重新统计,否则会由大量重复的计算;

3.就从当前right这个元素开始,往后找满足sum>=target的区间(当前right也有可能是满足的,因为++前的left可能很小,sum 剔除掉该left之后,依旧满足sum>=target );

4.这样就能省掉大量重复的计算,效率也会大大提升。

时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者最多都往后移动 n 次。因此时间复杂度是 O(N)

代码实现:
class Solution {
public:int minSubArrayLen(int target, vector<int>& nums) {int n=nums.size(),sum=0,len=INT_MAX;for(int left=0,right=0;right<n;right++){sum+=nums[right];//进窗口while(sum>=target){len=min(len,right-left+1);//更新结果sum-=nums[left++];//出窗口}}return len == INT_MAX?0:len;}
};

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

子数组:是数组中一个连续的部分

子串:是字符串中一个连续的部分

题目描述:

给定一个字符串 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 由英文字母、数字、符号和空格组成.

解法一:暴力枚举+哈希表(判断字符是否重复出现)O(N2)

算法思路:

枚举从每⼀个位置开始往后,无重复字符的子串可以到达什么位置。找出其中长度最大的即可。

在往后寻找无重复子串能到达的位置时,可以利用哈希表统计出字符出现的频次,来判断什么时候子串出现了重复元素。

代码实现:
class Solution {
public:int lengthOfLongestSubstring(string s) {int ret = 0; // 记录结果int n = s.length();// 1. 枚举从不同位置开始的最⻓重复⼦串// 枚举起始位置for (int i = 0; i < n; i++){// 创建⼀个哈希表,统计频次int hash[128] = { 0 };// 寻找结束为⽌for (int j = i; j < n; j++){hash[s[j]]++; // 统计字符出现的频次if (hash[s[j]] > 1) // 如果出现重复的break;// 如果没有重复,就更新 retret = max(ret, j - i + 1);}}// 2. 返回结果return ret;}
};

解法二:利用规律,使用“滑动窗口”来解决问题 O(N)

算法思路:

研究的对象依旧是一段连续的区间,因此继续使用滑动窗口思想来优化。

1.进窗口判断条件:

窗口内所有元素都是不重复的。

滑动窗口做法:

  1. left=0,right=0;

  2. 右端元素 ch 进入窗口的时候,哈希表统计这个字符的频次:

  • 如果这个字符出现的频次超过 1 ,说明窗口内有重复元素,那么就从左侧开始划出窗口(出窗口:从哈希表中删除该字符),直到 ch 这个元素的频次变为 1 ,然后再更新结果
  • 如果没有超过 1 ,说明当前窗口没有重复元素,可以直接更新结果,之后让下一个元素进入窗口(进窗口:让字符进入哈希表)。
代码实现:
class Solution {
public:int lengthOfLongestSubstring(string s) {int hash[128]={0};//使用数组来模拟哈希表int left=0,right=0,n=s.size();int ret=0;while(right<n){hash[s[right]]++;  //进入窗口while(hash[s[right]]>1)  //判断hash[s[left++]]--;  //出窗口ret=max(ret,right-left+1);  //更新结果right++; //让下一个元素进入窗口}return ret;}
};

3.最大连续 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。

解法一:暴力枚举+zero计数器

解法二:滑动窗口 O(N)

算法思路:

不要去想怎么翻转,不要把问题想的很复杂,这道题的结果无非就是一段连续的 1 中间塞了 k个 0 嘛。

因此,我们可以把问题转化成:找出数组中最长的子数组,其中0的个数不超过k个

既然是连续区间,可以考虑使用滑动窗口来解决问题。

1.进窗口判断条件:

子数组中0的个数不超过k个。

滑动窗口做法:

  1. left = 0 ,right = 0 ,zero=0;

  2. right<nums.size() ,下列一直循环:

  • 让当前元素进入窗口:如果是1,无视;如果是0,计数器zero++
  • zero > k 时:如果left所指是1,则无视;如果是0,计数器zero--left++
  • 更新结果:ret=max(ret,right-left+1) .
代码实现:
class Solution {
public:int longestOnes(vector<int>& nums, int k){int ret=0;for(int left=0,right=0,zero=0;right<nums.size();right++){if(nums[right]==0)zero++;//进窗口while(zero>k) //判断if(nums[left++]==0)zero--;  // 出窗口ret=max(ret,right-left+1);  //更新结果}return ret;}
};

4.将 x 减到 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

正难则反

问题转化:找出最长的子数组的长度,所有的元素的和target正好等于sum-x

算法思路:

题目要求的是数组左端+右端两段连续的、和为 x 的最短数组,信息量稍微多⼀些,不易理清思路;

正难则反,我们可以转化成最长的子数组的长度,所有的元素的和target正好等于sum-x

此时,就是熟悉的滑动窗口问题了。

算法流程:

1.进窗口判断条件:

target==sum-x

滑动窗口做法:

  1. left = 0 ,right = 0 ,tmp=0;
  2. 当前滑动窗口内数组和的变量记为sum=0;
  3. right<nums.size() ,下列一直循环:
  • 如果tmp<target,tmp+=nums[right]进窗口),right++,直至tmp>target or right越界;

  • 如果tmp>target,tmp-=nums[left]出窗口),left++,直至tmp<target or left越界;

  • 当经过前两步的左右移动使得 tmp == target更新结果

  1. 如果target<0,返回-1.

代码实现:

class Solution
{
public:int minOperations(vector<int>& nums, int x) {int sum = 0;for(int a : nums) sum += a;int target = sum - x;// 细节问题if(target < 0) return -1;int ret = -1;for(int left = 0, right = 0, tmp = 0; right < nums.size(); right++){tmp += nums[right]; // 进窗⼝while(tmp > target) // 判断tmp -= nums[left++]; // 出窗⼝if(tmp == target) // 更新结果ret = max(ret, right - left + 1);}if(ret == -1) return ret;else return nums.size() - ret;}
};

时间复杂度为O(N).

最后,本篇文章到此结束,感觉不错的友友们可以一键三连支持一下笔者,有任何问题欢迎在评论区留言哦~

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

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

相关文章

中电金信:院长寄语|关于源启AI+行动的思考

中国电子首席科学家 中电金信研究院院长 况文川 自2022年8月19日发布以来&#xff0c;源启已经走上了她第三年的征途。今天&#xff0c;源启已经成为公司战略的支点&#xff0c;中电金信正致力于用“源启底座”“源启咨询”“源启应用重构”三位一体的方式来赋能千行百业数智化…

海康私有化视频平台EasyCVR视频分析设备平台流媒体协议RTMP、HTTP-FLV、HLS的简单对比

在当今的数字化世界中&#xff0c;视频流协议的选择对于确保流畅、高效的视频传输至关重要。随着互联网技术的快速发展&#xff0c;直播和视频点播服务已经成为人们日常生活中不可或缺的一部分。无论是安防监控、在线教育、远程会议还是娱乐直播&#xff0c;用户对于视频流的实…

详解使用python读写csv,以及将csv数据写入数据库

csv文件 csv介绍 CSV&#xff0c;也即Comma-Separated Values&#xff0c;是一种用于存储表格数据的纯文本文件格式&#xff0c;其中每一行代表一条记录&#xff0c;记录中的各个字段由逗号分隔。 姓名,年龄,性别 张三,25,男 李四,28,男 王五,22,男 六六,29,女 子柒,28,女 对…

OpenMVS OpenMVG 笔记

OpenMVS & OpenMVG 笔记 OpenMVS 和 OpenMVG 都是计算机视觉中用于三维重建的开源库。两者都可以实现从图像集合中计算出相机位姿和三维点云&#xff0c;但它们的重点略有不同。 OpenMVG 主要关注于从输入图像集合中提取稠密的特征匹配&#xff0c;通过这些匹配计算相机的…

Golang--文件操作

1、文件 文件&#xff1a;文件用于保存数据&#xff0c;是数据源的一种 os包下的File结构体封装了对文件的操作&#xff08;记得包os包&#xff09; 2、File结构体--打开文件和关闭文件 2.1 打开文件 打开文件&#xff0c;用于读取&#xff08;函数&#xff09;&#xff1a; 传…

dcdc3节锂电池串联9-12V升压32V 3A/5A 音响供电恒压芯片 SL4010

SL4010&#xff1a;高效能9-12V至32V升压解决方案&#xff0c;为高端音响系统注入澎湃动力 在追求极致音质与持久续航的音频世界里&#xff0c;SL4010 DC-DC升压转换器以其卓越的性能和可靠性&#xff0c;成为高端音响系统的理想供电伙伴。专为3节锂电池串联&#xff08;9-12V…

onnx-web + yolov8n 在视频流里做推理

顺着我上一篇文章 使用onnxruntime-web 运行yolov8-nano推理 继续说&#xff0c;有朋友在问能不能接入 视频流动&#xff0c;实时去识别物品。 首先使用 getUserMedia 获取摄像头视频流 getUserMedia API 可以访问设备的摄像头和麦克风。你可以使用这个 API 获取视频流&#…

力扣题库——136.只出现一次的数字

代码实现&#xff1a; class Solution { public:int singleNumber(vector<int>& nums) {int result0;for(int num:nums){result^num;}return result;} }; 结果&#xff1a; 思路&#xff1a;这里让0和数组元素不断异或&#xff0c;因为0与一个数异或的结果是它本身…

EasyPOI使用详解

EasyPOI 简介 easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员 就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板 语言(熟悉的表达式语法),完成以前复杂的写法 文档&#xff1a;http://easypoi.mydoc.io/#categor…

JAVA设计模式之【建造者模式】

1 定义 建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 2 类图 产品类&#xff08;Product&#xff09;&#xff1a;表示被创建的复杂…

智能化健身房管理:Spring Boot与Vue的创新解决方案

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

如何修改WordPress经典编辑器的默认高度?

boke112百科有一个使用WordPress搭建的小网站&#xff0c;文章内容就是几个字不到一行&#xff0c;但是每次使用经典编辑器编辑文章时&#xff0c;都觉得编辑器默认高度太高了&#xff0c;影响了我添加文章摘要和其他属性&#xff0c;有没有办法修改WordPress经典编辑器的默认高…

C#属性 Property

属性Property不是变量。 它们是由名为访问器方法来实现的一种方法。 实例属性表示的是实例的某个数据&#xff0c;通过这个数据反映实例当前的状态 静态属性表示的是类型的某个数据&#xff0c;通过这个数据反映类型当前的状态 意义&#xff1a; 防止恶意赋值(通过属性间接访问…

【力扣热题100】[Java版] 刷题笔记-121. 买卖股票的最佳时机

题目&#xff1a;121. 买卖股票的最佳时机 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。…

Wi-Fi7 puncturing技术增强与应用

原文关注公众号 - 无线技术栈,及时查看网络/Wi-Fi更多知识 “本文图片没有一一列出,感兴趣可以关注公众号 - 无线技术栈” “本文图片没有一一列出,感兴趣可以关注公众号 - 无线技术栈” Puncturing是一种有效的编码技术,广泛应用于无线通信中,用于在保持信号的可靠性的同…

C语言内存函数介绍和模拟实现:(memcpy,memmove,memcmp,memset)

memcpy介绍及模拟实现&#xff1a; memcpy介绍&#xff1a; void* 是指可以接受任何类型的指针。 memcpy是把从 source 指针开始之后的 num 个字节的内存拷贝到 destination 指针之后的空间。 遇到‘\0’不会停止&#xff0c;而且memcpy不可以拷贝重叠空间&#xff0c;就是说…

浏览器指纹修改指南2024 - 修改Geolocation API指纹(十一)

引言 在前几篇文章中&#xff0c;我们已经详细探讨了Geolocation API的定义、作用及其在浏览器指纹中的重要性&#xff0c;并深入分析了Chromium源码中Geolocation API的实现位置和修改方法。通过这些分析&#xff0c;我们为后续的修改工作奠定了坚实的基础。 在本篇文章中&a…

【微信小程序】基本语法

一、导入小程序 选择代码目录 项目配置文件 appid 当前小程序的 AppIDprojectname 当前小程序的项目名称 变更AppID&#xff08;视情况而定&#xff0c;如果没有开发权限时需要变更成个人的 AppID&#xff09; 二、模板语法 在页面中渲染数据时所用到的一系列语法叫做模板…

数据结构:顺序表

顺序表 顺序表的概念与结构静态顺序表动态顺序表 动态顺序表的实现SeqList.h的创建初始化动态顺序表&#xff08;LS_Init&#xff09;动态顺序表的销毁&#xff08;LS_Destry&#xff09;检查动态内存空间是否已满&#xff08;SL_CheckCapacity&#xff09;动态顺序表打印有效数…

MySQL_数据类型建表

复习&#xff1a; 我们昨天学习的知识都忘了嘛&#xff1f;如果忘了也不要担心&#xff0c;我来带大家来复习一遍吧&#xff01;&#xff01;&#xff01; 1.查看所有数据库 show databases;2.创建属于自己的数据库 create database 数据库名; 检查自己创建的数据库是…