算法刷题笔记 滑动窗口(C++实现,非常详细)

news2024/11/27 4:19:47

文章目录

    • 题目描述
    • 基本思路
    • 实现代码

题目描述

  • 给定一个大小为n ≤ 10^6的数组。
  • 有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。
  • 你只能在窗口中看到k个数字。
  • 每次滑动窗口向右移动一个位置。以下是一个例子:
    • 该数组为 [1 3 -1 -3 5 3 6 7],k为3
      在这里插入图片描述
  • 你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

  • 输入包含两行。
  • 第一行包含两个整数nk,分别代表数组长度和滑动窗口的长度。
  • 第二行有n个整数,代表数组的具体数值。同行数据之间用空格隔开。

输出格式

  • 输出包含两个。
  • 第一行输出,从左至右,每个位置滑动窗口中的最小值。
  • 第二行输出,从左至右,每个位置滑动窗口中的最大值。

基本思路

这道题是单调队列系列题目的最基础的模板题,但是对于像我这样的初学者来说仍然难度较大,因此我将对该题的思路进行详细解析。

  • 算法题的核心是将一个有直观、暴力的思路的算法优化为一个逻辑上更加复杂,但是时间和空间上更占优势的算法。那么,对于这道题,我们就需要首先思考蛮力算法的求解步骤是什么。本题的蛮力算法非常直观,就是一个简单的双重遍历。例如,对于数组[1 3 -1 -3],并假设窗口大小为3,那么我们就从数组的第一个元素开始遍历,向右滑动窗口,每次遍历到的数组元素作为窗口的左端点。以这个例子为例,可以得到两个窗口[1 3 -1][3 -1 -3],再分别从这两个窗口中找出最大值和最小值即可。代码实现上,可以大致如下:
#include <cstdio>
#include <vector>
using namespace std;

// 本例子中n为4,k为3,假设原始数组为arr,本例子以最大值为例
for(int i = 0; i < n - k + 1; ++ i)
{
    // 使用一个向量表示当前窗口,并向该窗口中添加元素
    vector<int> window;
    for(int j = i; i < i + k; ++ j) window.push_back(arr[j]);
    // 查找该向量中的最大值并输出
    int max = window[0];
    for(int j = 1; j < window.size(); ++ j) if(window[j] > max) max = window[j];
    printf("%d ", max);
}
  • 但是,我们仔细考虑一下,这种方法存在明显的冗余性。两个相邻的滑动窗口之间,有且只有一个元素不相同,而窗口中的其他元素都是完全一样的,因此会经过多轮重复遍历,创建多个大部分元素都相同的向量,算法的时间复杂度为O(nk)。既然存在冗余元素,那么我们就需要从数据结构和算法的角度上考虑对算法进行优化。
  • 直观上,我们可以发现既然相邻的两个窗口只有一个元素存在区别,即相当于下一个窗口的元素是去除了上一个窗口中的首元素,并且在后面添加了一个新元素,这就很类似于数据结构中常用的队列数据结构。因此,如果能够使用队列来代替向量,那么就可以提高算法的效率。实现代码如下:
#include <cstdio>
#include <deque>
using namespace std;

// 首先创建一个队列,并以第一个窗口中的元素进行初始化
deque<int> window;
for(int i = 0; i < k; ++ i) window.push_back(arr[i]);
// 每轮遍历队首元素出栈,并从队尾入队一个元素
for(int i = k; i < n - k + 1; ++ i)
{
    window.pop_front();
    window.push_back(arr[i]);
    // 查找当前队列中的最大值
    int max = window.front();
    for(int item : window) if(item > max) max = item;
    printf("%d ", max);
}
  • 基于队列的实现代码的确能够有更高的时间效率,但是是否可以进一步优化呢?我们发现,尽管使用队列可以更加方便地创建和维护一个窗口,而不像向量那样需要每次完全重新新建一个,但是在查找最大值时,仍然需要遍历整个队列。如果我们想要继续提高效率,就必须简化查找过程,避免耗时的循环遍历,此时就应该使用单调队列进行处理。

  • 仍然以[1 3 -1 -3]为例,当我们每一轮循环更新队列时,我们可以修改我们的更新策略。下面进行举例说明,以查找最大值为例。

    • 第一轮:队列初始为空,因此直接将第一个元素1放入队尾即可。
    • 第二轮:队列目前为[1],当前遍历到的元素为3;由于3大于当前的队尾元素1,因此如果3也放入队尾后,在查找最大值的过程中,元素1一定不会成为任何一个窗口的最大值了。这是因为当元素1和元素3在同一个窗口中时,31大,因此最大值不可能是1;当元素1和元素3不在同一个窗口中时,只有一种可能,就是当前窗口中已经不包含1了,这是因为3在原始数组中排在1的后面,只要1在窗口中,3一定在窗口中,所以这种情况下,窗口中已经不包含有元素1,所以自然不会成为最大值。因此,可以认为队列中的1为冗余元素,可以直接将其出队。只有某个元素可能成为某个窗口的最大值时,才会被放入队尾进入队列中,而所有确定下来的冗余元素都出队。所以,在第二轮迭代中首先通过上述比较过程,让队尾的1出队,此时队列为空,则直接把当前元素3放入队列中。
    • 第三轮:队列目前为[3],当前遍历到的元素是-1。由于队尾元素3-1更大,因此直接将-1入队放入队尾即可,这是因为3会在-1之前从队头离开队列,此时-1就有可能成为某个窗口的最大值元素。
    • 第四轮:和第三轮类似,队列目前是[3 -1],当前遍历到的元素是-3。由于队尾元素-1-3更大,因此直接将-3放入队尾。
  • 那么,应该如何确定何时要将队头元素出队呢?队头元素出队表示该元素已经不在当前的窗口中,最简单的处理方法就是用另一个队列记录所有队列中的元素在数组中的下标,并在每一轮的遍历过程中,通过下标判定队首元素是否在窗口中即可。下标队列和元素队列中的元素应该是一一对应的,需要同时添加和同时删除。

实现代码

#include <cstdio>
#include <deque>
using namespace std;

const int N = 1e6 + 10;
int arr[N];

int main(void)
{
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; ++ i) scanf("%d", &arr[i]);
    deque<int> q;
    deque<int> index;
    // 最小值部分
    for(int i = 0; i < n; ++ i)
    {
        while(!q.empty() && q.back() >= arr[i])
        {
            q.pop_back();
            index.pop_back();
        }
        q.push_back(arr[i]);
        index.push_back(i);
        if(i >= k && i - k == index.front())
        {
            q.pop_front();
            index.pop_front();
        }
        if(i > k - 2) printf("%d ", q.front());
    }
    // 清空两个队列,对称地求解最大值
    q.clear();
    index.clear();
    printf("\n");
    for(int i = 0; i < n; ++ i)
    {
        while(!q.empty() && q.back() <= arr[i])
        {
            q.pop_back();
            index.pop_back();
        }
        q.push_back(arr[i]);
        index.push_back(i);
        if(i >= k && i - k == index.front())
        {
            q.pop_front();
            index.pop_front();
        }
        if(i > k - 2) printf("%d ", q.front());
    }
    return 0;
}

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

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

相关文章

SAP PS学习笔记02 - 网络,活动,PS文本,PS文书(凭证),里程碑

上一章讲了PS 的概要&#xff0c;以及创建Project&#xff0c;创建WBS。 SAP PS学习笔记01 - PS概述&#xff0c;创建Project和WBS-CSDN博客 本章继续讲PS的后续内容。包括下面的概念和基本操作&#xff0c;以及一些Customize&#xff1a; - 网络&#xff08;Network&#xf…

CC工具箱使用指南:【相交占比分析】

一、简介 需求场景如下&#xff0c;有【待分析地块】和【面积占比参考】2个图层。2个图层之间存在空间上的重叠。工具的目的是为了分析出【待分析地块】的每1个图斑中&#xff0c;和【面积占比参考】相交的面积&#xff0c;以及和总面积的占比。 举一个应用场景为例&#xff0…

java信号量(Semaphore)

Java中的信号量&#xff08;Semaphore&#xff09;是一种用于控制多个线程对共享资源的访问的同步工具。它可以用来限制可以同时访问某些资源的线程数量。Semaphore 提供了一个计数器来管理许可证的获取和释放&#xff0c;每个许可证代表对资源的一次访问权限。 import java…

阶段三:项目开发---搭建项目前后端系统基础架构:任务11:搭建项目后台系统基础架构

任务描述 1、了解搭建民航后端框架 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL、Redis的Java项目 3、以原项目为参照搭建项目所涉及到的各个业务和底层服务 4、以原项目为例&#xff0c;具体介绍各个目录情况并参照创建相关文件夹 任务指导 1、讲框架的选择和原理 …

解决Unable to Correct Problems ‘You have Held Broken Packages’

进入 Software & Updates 后下拉 Download from&#xff0c;点击 Other… 点击 Select Best Server 等待测试服务器 测试完成后会默认标红测试出的最好的那个服务器&#xff0c;直接点击 Choose Server&#xff0c;可能需要输入系统用户密码5. 输入然后返回上级界面 点击 C…

实现ubuntu的任务计划反弹shell

1.实验目的 使用Ubuntu定时任务反弹shell 2实验环境 ubuntu&#xff1a;ip地址&#xff1a;192.168.80.133 kali&#xff1a;ip地址&#xff1a;192.168.80.134 3.编写crontab计划任务 在ubuntu的系统中使用crontab -e命令编写计划任务 作用&#xff1a;是将一个交互式的…

STM32利用FreeRTOS实现4个led灯同时以不同的频率闪烁

在没有接触到FreeRTOS时&#xff0c;也没有想过同时叫两个或两个以上的led灯闪烁的想法&#xff0c;接触后&#xff0c;发现如果想叫两个灯同时以不同的频率闪烁&#xff0c;不能说是不可能&#xff0c;就算是做到了也要非常的麻烦。但是学习了FreeRTOS后&#xff0c;发现要想同…

26 华三防火墙安全区域

防火墙区域规划 配置网络网卡的地址在同一网段 第一个问题 为什么防火墙直连在同一个网段ping不通? 配置IP地址 local区域: 将local区域的所有接口启用 华三防火墙的local区域是指设备本地接口所在的区域&#xff0c;也称为局域网&#xff08;LAN&#xff09;或内部网络 Int…

机器学习与深度学习:区别(含工作站硬件推荐)

一、机器学习与深度学习区别 机器学习&#xff08;ML&#xff1a;Machine Learning&#xff09;与深度学习&#xff08;DL&#xff1a;Deep Learning&#xff09;是人工智能&#xff08;AI&#xff09;领域内两个重要但不同的技术。它们在定义、数据依赖性以及硬件依赖性等方面…

硬件开发笔记(二十四):贴片电容的类别、封装介绍,AD21导入贴片电容、原理图和封装库3D模型

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140241817 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

VUE3初学入门-02-VUE创建项目

创建VUE项目的另一个方法 三种方法通过vue-cli进行创建通过npm进行创建比较 部署到nginx修改配置生成部署文件 三种方法 上一篇是在VSCODE中建立工作区&#xff0c;然后创建&#xff0c;属于命令加鼠标方式。个人感觉&#xff0c;在VSCODE基本上都是这样的操作&#xff0c;不是…

C++模板元编程(一)——可变参数模板

这个系列主要记录C模板元编程的常用语法 文章目录 引言语法应用函数模板可变参数的打印可变参数的最小/最大函数 类模板 参考文献 引言 在C11之前&#xff0c;函数模板和类模板只支持含有固定数量的模板参数。C11增强了模板功能&#xff0c;允许模板定义中包含任意个(包括0个)…

Pytorch 实践手写数字识别深度学习网络 LeNet-5

Pytorch 实践手写数字识别深度学习网络 LeNet-5 文章目录 Pytorch 实践手写数字识别深度学习网络 LeNet-5认识 LeNet-5认识数据集处理数据集下载数据集读取数据定义Dataset的继承类把数据进行载入载入dataloader 编写网络编写训练与测试代码实践结果展示完整代码 训练手写体识别…

什么是反射?

什么是反射&#xff1f; 1、反射的基本概念2、 获取Class对象3、获取类的成员变量、方法和构造方法3.1 获取成员变量3.2 获取方法3.3 获取构造方法3.4 动态调用方法 4、反射的优缺点 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 反射&…

Unity3D 转换微信小游戏指引 02

Unity3D 转换微信小游戏指引系列&#xff08;第二期&#xff09; 云开发 当小游戏打包后的首包占用内存比较大&#xff08;大约是 14M 左右&#xff09;&#xff0c;首包资源加载方式就不能选择小游戏包内了。 这时就需要购买服务器&#xff0c;把首包放到服务器上&#xff…

Drools开源业务规则引擎(二)- Drools规则语言(DRL)

文章目录 1.DRL文件的组成&#xff1a;2.package3.import4.function5.query6.declare7.global8.rule8.1.规则属性8.2.LHS8.2.1.语法格式8.2.2.运算符优先级8.2.3.特殊的运算符1.matches, not matches2.contains, not contains3.memberOf, not memberOf4.in, notin5.soundslike6…

尚品汇-(十三)

&#xff08;1&#xff09;查询sku列表 在ManageService 中添加 /*** SKU分页列表* param pageParam* return*/ IPage<SkuInfo> getPage(Page<SkuInfo> pageParam);接口实现类 Override public IPage<SkuInfo> getPage(Page<SkuInfo> pageParam) {Qu…

STM32-01 推挽输出-点亮LED

本文以STM32中点亮LED为例&#xff0c;解读推挽输出的原理 推挽输出介绍 所谓的推挽输出&#xff0c;就是通过控制输出控制模块&#xff0c;打开或者关闭P-MOS或者N-MOS。 ─ 推挽模式下&#xff1a;输出寄存器上的’0’激活N-MOS&#xff0c;而输出寄存器上的’1’将激活P-M…

IDEA与通义灵码的智能编程之旅

1 概述 本文主要介绍在IDEA中如何安装和使用通义灵码来助力软件编程,从而提高编程效率,创造更大的个人同企业价值。 2 安装通义灵码 2.1 打开IDEA插件市场 点击IDEA的设置按钮,下拉选择Plugins,如下: 2.2 搜索通义灵码 在搜索框中输入“通义灵码”,如下: 2.3 安…

74HC165芯片验证

目录 0x01 74HC165芯片介绍0x02 编程实现 0x01 74HC165芯片介绍 74HC165的引脚定义如下&#xff0c;长这个样子 ABCDEFGH是它的八个输入引脚&#xff0c;例如你可以将它连接按键&#xff0c;让它来读取8个按键值。也可以将他级联其它的74165&#xff0c;无需增加单片机GPIO引…