【C++算法图解专栏】一篇文章带你掌握尺取法(双指针)

news2024/11/26 3:39:58

✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📣专栏定位:为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解,也欢迎大佬们一起交流~
📚专栏地址:https://blog.csdn.net/Newin2020/article/details/126445229
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
🎏唠叨唠叨:在这个专栏里我将会整理 PAT 甲级的真题题解,并将他们进行分类,方便大家参考。

尺取法(双指针)

这一讲我们来介绍一个非常常用的算法 —— 尺取法,一般称为双指针算法,下文也将用这种说法。这种算法应用场景挺广,在很多题目中只是作为解出题目的其中一个关键部件,下面我将给没接触过的小伙伴详细讲解,会从模板题入手,不会直接上综合题,这点大家放心~

原理

双指针算法是一个优化算法,注意解决一些区间相关的问题,它可以将一个双循环优化成一个单循环,即将 O(n2) 的时间复杂度讲到 O(n),但并不是所有双循环都能优化成单循环,需要看应用的场景。

for(int i = 0; i < n; i++)             //i从头扫到尾
	for(int j = n-1; j >= 0; j--)     //j从尾扫到头
    { ... }  
//双指针优化后:
for (int i = 0, j = n - 1; i < j; i++, j--) 
{ ... }

最长连续不重复子序列

【题目地址】https://www.acwing.com/problem/content/801/

我们先来看第一道模板题,我会在讲题目的过程中给大家介绍双指针的用法,帮助大家能够更深刻的理解。

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 n。

第二行包含 n 个整数(均在 0∼105 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围

1≤n≤105

输入样例:

5
1 2 2 3 5

输出样例:

3

题目要求我们找到最长的不包含重复的数的连续区间长度,先来思考最暴力的做法,即需要枚举左右端点,根据左端点然后往后枚举右端点,直至出现重复的数为止。可以发现这种做法会出现大量的重复遍历,这时双指针算法就派上用场了,我们可以利用一个滑动窗口进行模拟,直接上步骤:

  1. 这里需要用到两个数组,一个数组 a 用来存储序列,一个数组 s 用来存储每个数字出现的次数,至于有何作用接着往下看。另外就是本题核心双指针算法,需要用到两个指针 ij 来表示滑动窗口的右边界左边界,初始时刻 ij 都为 0

  2. 可以边遍历边输入,为了方便大家理解,下面用一个例子带着大家模拟一遍,就拿题目样例举例,假设给定一个序列 {1, 2, 2, 3, 5},然后开始模拟(下面图解中红色区域代表当前滑动窗口中的元素)。

    第一步,输入第一个数 1,并令 s[a[i]]++s[1]++ 表示目前窗口当中 1 的个数加 1,然后发现此时窗口中并没有重复的元素,故更新窗口长度的最大值 res=max(res,i-j+1)=max(0,1)=1,然后将右边界指针 i 右移一位。

    第二步,输入第二个数 2,与步骤一相同,统计对应更新对应数字的数量即 s[2]++ ,且没有重复元素出现,故更新窗口长度的最大值 res=max(res,i-j+1)=max(1,2)=2,然后将右边界指针 i 右移一位。

    第三步,输入第三个数 2,同样令 s[2]++,发现加入的元素 2 窗口里已经出现过了,因为 s[2]>1 即元素 2 的数量已经大于 1

    这时候关键点就来了,我们要从窗口的左边界 j 开始删除元素,因为要保证序列是连续的。每次删除元素后都将对应 s[a[j]] 减去 1,即动态更新数组 s,直到发现 s[a[j]] 的值不大于 1 为止。

    并更新窗口长度的最大值 res=max(res,i-j+1)=max(2,1)=2,然后将右边界指针 i 右移一位。

    第四步,同上。发现没有重复元素,更新窗口长度的最大值。

    第五步,同上。发现并没有重复元素,最终窗口长度的最大值更新为 3,结束遍历。

  3. 输出最长连续不重复子序列的长度,即窗口长度的最大值。

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
int s[N], a[N], n;

int main()
{
    cin >> n;
    int res = 0;
    for (int i = 0, j = 0; i < n; i++)
    {
        cin >> a[i];
        s[a[i]]++;
        while (s[a[i]] > 1)
        {
            s[a[j]]--;
            j++;
        }
        res = max(res, i - j + 1);
    }
    cout << res;
    return 0;
}

数组元素的目标和

【题目地址】https://www.acwing.com/problem/content/802/

上面那题可以发现两个指针都是正向进行扫描即两指针扫描的方向一致,现在我们来看一道反向扫描的案例。

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。

数组下标从 00 开始。

请你求出满足 A[i]+B[j]=x 的数对 (i,j)。

数据保证有唯一解。

输入格式

第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。

第二行包含 n 个整数,表示数组 A。

第三行包含 m 个整数,表示数组 B。

输出格式

共一行,包含两个整数 i 和 j。

数据范围

数组长度不超过 105
同一数组内元素各不相同。
1≤数组元素≤109

输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1

注意题目给定的是两个有序的序列,这样就方便我们使用双指针算法。老样子先看暴力做法是什么,最暴力的就是先遍历数组 A 的元素,然后再对 A 中每个元素都遍历一遍 B 数组中的元素,然后输出元素之和为目标值 x 的数对。这样子做的时间复杂度为 O(n2),而双指针算法就可以将改时间复杂度降为 O(n),直接上步骤:

  1. 这里除了要用到两个数组 AB 来存储元素外,还需要用到两个指针 ij 分别指向数组 AB 的元素。现在关键点来了,我们不能像上题一样让两个指针扫描同一方向,这样会漏掉很多情况,例如一个小值加上一个大值等于目标值。所以需要反向进行扫描,即指针 ij 初始化时分别指向数组 A 的一个元素和数组 B 的最后一个元素。
  2. 现在开始进行扫描,指针 i 从前往后对数组 A 进行扫描,指针 j 从后往前对数组 B 进行扫描。这其实用到了有序序列的性质,可以保证我们不会漏掉任何一种情况,而扫描时判断的规则如下:
    1. 让指针 i 固定往后走,即每一趟 i 都只往后移一位,然后根据 j 指向的值进行判断。
    2. 如果 A[i]+B[j]>x,说明两指针指向的元素之和大于目标和,现在我们想要这个和更小一点,而指针 j 遍历的方向就是从大到小,故将指针 j 往前移直到元素之和小于等于目标和为止。
    3. 如果此时元素之和等于目标值,则直接输出结果即可。反之,进行下一趟遍历即将指针 i 后移一位,因为此时元素之和已经小于等于目标值,我想获得不同的数对或让它更大一点,而指针 i 遍历的方向就是从小到大,故移动 i 最合适。

为了加深理解,还是拿题目样例进行模拟,假设给定两个有序数组 A = {1, 2, 4, 7} 和 B = {3, 4, 6, 8, 9},以及一个目标值 x = 6。

首先,i=0 指向数组 A 的第一个元素,j=4 指向数组 B 的最后一个元素,然后进行判断,发现 1+9=10 大于目标值 6,故将指针 j 往前移动直至元素之和小于等于目标值。

发现此时元素之和 1+4=5 小于目标值 6,故将 i 往后移一位再重复上面的操作。

结果发现此时两指针指向的元素之和 2+4=6 等于目标值 6,故直接输出该数对的下标。后面继续上述操作,发现不再出现元素之和等于目标值的数对,结束遍历。

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
int A[N], B[N], n, m, x;

int main()
{
    cin >> n >> m >> x;
    for (int i = 0; i < n; i++)    scanf("%d", &A[i]);
    for (int i = 0; i < m; i++)    scanf("%d", &B[i]);
    for (int i = 0, j = m - 1; i < n; i++)
    {
        while (j >= 0 && A[i] + B[j] > x)    j--;
        if (j >= 0 && A[i] + B[j] == x)  printf("%d %d\n", i, j);
    }
    return 0;
}

总结

恭喜您成功点亮双指针算法技能点!

通过两道模板题可以发现双指针其实没有我们想象中的那么难,它只是作为一个很小的部件出现在我们的代码当中。第一题双指针是正向扫描,第二题双指针是反向扫描,根根据不同的题型会有不同的应用。

双指针在我们平时写题过程中应用非常广泛,所以这就需要大家多去刷题见识不同的题型了~

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

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

相关文章

java spring IOC xml方式工厂Bean操作

spring中有两种Bean 第一种 普通 Bean 就是我们在配置文件中 定义的类对象 创建bean 并定义相关的id和声明类对象 普通bean的特点在于 在配置文件中定义的类型 也就是返回类型 就比如 你定义的是一个 book类的类型 那你这个bean返回的 也比如是个book类型的对象 第二种 是在工…

模拟实现vector/迭代器失效问题

对于STL&#xff0c;我们不仅学会使用STL,还要了解其底层原理&#xff0c;这样一来&#xff0c;我们就能知道什么时候用string好&#xff0c;什么时候用vector&#xff0c;什么时候用list&#xff0c;哪种方法效率高等等。其次了解了STL的底层原理&#xff0c;也助于我们的C功力…

RTF、RIR、Steering Vector傻傻分不清

RTF&#xff1a; Relative transfer function&#xff0c;相对传递函数RIR: Room impulse response&#xff0c;空间冲击响应Steering vector: 导向矢量场景问题定义&#xff1a;空间中存在I个麦克风和J个声源&#xff0c;麦克风采集到的信号其中&#xff0c;麦克i的信号其中表…

一起自学SLAM算法:9.1 ORB-SLAM2算法

连载文章&#xff0c;长期更新&#xff0c;欢迎关注&#xff1a; 下面将从原理分析、源码解读和安装与运行这3个方面展开讲解ORB-SLAM2算法。 9.1.1 ORB-SLAM2原理分析 前面已经说过&#xff0c;ORB-SLAM2算法是特征点法的典型代表。因此在下面的分析中&#xff0c;首先介绍一…

被删库勒索了,怎么使用docker进行MySQL容器的管理?

大家觉得写还可以&#xff0c;可以点赞、收藏、关注一下吧&#xff01; 也可以到我的个人博客参观一下&#xff0c;估计近几年都会一直更新&#xff01;和我做个朋友吧&#xff01;https://motongxue.cn 起因&#xff1a;云服务器MySQL密码设置的太简单了&#xff0c;导致到被入…

路由策略实验

1.先配置IP和环回 [Huawei]sysname R1 [R1]interface GigabitEthernet 0/0/0 [R1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [R1-GigabitEthernet0/0/0]int g 0/0/1 [R1-GigabitEthernet0/0/1]ip add 22.1.1.1 24 [R1-GigabitEthernet0/0/1]q [R1]int l 0 [R1-LoopBack0]ip ad…

ETHDenver 2023 的 Cartesi BUIDLathon 项目创意

希望你在了解Cartesi之前&#xff0c;谨慎对待自己的行为。一旦你开始研究并搜寻可以使用Cartesi Rollups构建的项目或者应用&#xff0c;你就会陷入一个令人兴奋的螺旋洞穴中&#xff0c;你会上瘾。如果你想在2023年中建造一些很具有意义的事情&#xff0c;那你就来对地方了。…

Python01概述 基础语法 判断

Python概述 第二章-Python基础语法 01-字面量 02-注释 03-变量 04-数据类型 05-数据类型转换 06-标识符 07-运算符 08-字符串的三种定义方式 09-字符串的拼接 10-字符串格式化 11-字符串格式化的精度控制 12-字符串格式化的方式-快速写法 13-对表达式进行格式化 14-字符串格…

Java语法核心——面向对象编程

目录 面向过程思想概述 面向对象思想概述 面向对象思想特点及举例 类与对象的关系 类的定义 类与对象的案例(demo02) 对象内存存储机制 成员变量和局部变量的区别 private关键字 面向过程思想概述 我们回想一下&#xff0c;这几天我们完成一个需求的步骤&#xff1a;首…

echarts数据可视化项目搭建(一)

目录直角坐标系通用配置项tooltiptoolboxlegenddataZoom柱状图常见效果折线图常见效果散点图常见效果其他坐标系饼图基本实现常见效果地图地图基本展示不同城市颜色不同地图与散点图结合雷达图仪表盘本博客内容参考黑马课程&#xff0c;详细信息请参考以下网址 Bilibili官方黑…

Apache Superset 开源商业智能大数据可视化

Apache Superset 是一款现代化的开源大数据工具&#xff0c;也是企业级商业智能 Web 应用&#xff0c;用于数据探索分析和数据可视化。 Apache Superset 是一个适合企业日常生产环境中使用的商业智能可视化工具。它具有快速、轻量、直观的特点&#xff0c;任何用户都可以轻松地…

Spring Boot学习之Shiro

文章目录零 全部源码地址一 Shiro简介1.1 Shiro功能1.2 Shiro架构&#xff08;外部视角&#xff09;1.3 Shiro架构&#xff08;内部视角&#xff09;二 Shiro快速入门2.1 演示代码&部分源码解读三 Spring Boot集成Shio3.0 准备操作3.1 整合Shiro3.2 页面拦截实现3.3 登录认…

ESP32设备驱动-HMC5983磁力计驱动

HMC5983磁力计驱动 1、HMC5983介绍 霍尼韦尔 HMC5983 是一款温度补偿型三轴集成电路磁力计。这种表面贴装、多芯片模块专为汽车和个人导航、车辆检测和指向等应用的低场磁场传感而设计。 HMC5983 包括我们最先进的高分辨率 HMC118X 系列磁阻传感器和一个 ASIC,该 ASIC 包含…

AOP切面编程

前言&#xff1a;AOP&#xff08;Aspect Oriented Programming&#xff09;是一种设计思想&#xff0c;是软件设计领域中的面向切面编程&#xff0c;它是面向对象编程的一种补充和完善&#xff0c;它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态…

各种Sequence Self-Attention变形 (加速矩阵运算 且保证全局特征)

人工设计Self-attention的N*N矩阵1. Local Attention/Truncated Attention2. Stride Attention3. Global Attention人工设计Self Attention的使用与选择1.LongFomer2.Big Bird自动设计Self Attention的N*N矩阵1. Reformer2.Sinkborn Sorting Network不需要N*N大小的矩阵1.Linfo…

【python】图片转字符画 cv2+pygame实现

网上看到一些字符画,非常羡慕,想要用python写一个类似的东西,突然想到字符画不就是把图片分割为像素块再进行替换嘛 恰好之前稍稍入门了python的opencv库,可以对图片进行处理。 处理图片的思想为:对一个区域的像素进行参考值计算,用具有相似参考值的字符进行替代,因此除…

打工人必学的法律知识(七)——《中华人民共和国劳动合同法实施条例》

目录 来源 第一章 总 则 第二章 劳动合同的订立 第三章 劳动合同的解除和终止 第四章 劳务派遣特别规定 第五章 法津责任 第六章 附 则 来源 《中华人民共和国劳动合同法实施条例》 第一章 总 则 第一条 为了贯彻实施《中华人民共和国劳动合同法》&#xff08;以下简称…

mybatis说明

目录 1.说明 2.配置文件 3.映射器 4.select标签 5.insert标签 6.update标签 7.delete标签 8.resultMap的特别说明 9.注解 10.关联(级联)查询 11.动态sql 12.mybatis分页 13缓存 1.说明 MyBatis 是一个开源、轻量级的数据持久化框架&#xff0c;是 JDBC 和 Hiberna…

Redis学习笔记:数据结构和命令

本文是自己的学习笔记。主要参考资料如下&#xff1a; 马士兵 4、Redis的五大数据类型1.1、String1.1.1、String 类型的命令1.1.2、存储对象1.2、List1.2.1、List基本命令1.2.2、List高级命令1.3、Set1.3.1、Set基本命令1.4、HashMap1.4.1、HashMap基本命令1.5、ZSet&#xff0…

【数据结构】7.4 散列表的查找

文章目录7.4.1 散列表的基本概念7.4.2 散列函数的构造散列函数的构造方法7.4.3 处理冲突的方法1. 开地址法1.1 线性探测法1.2 二次探测法2. 链地址法7.4.4 散列表的查找散列表的查找效率分析总结7.4.1 散列表的基本概念 基本思想&#xff1a;根据要存储的关键字的值&#xff0…