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

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

尺取法(双指针)

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


原理

双指针算法是一个优化算法,注意解决一些区间相关的问题,它可以将一个双循环优化成一个单循环,即将 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 用来存储每个数字出现的次数,至于有何作用接着往下看。另外就是本题核心双指针算法,需要用到两个指针 i 和 j 来表示滑动窗口的右边界与左边界,初始时刻 i 和 j 都为 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.这里除了要用到两个数组 A 与 B 来存储元素外,还需要用到两个指针 i 和 j 分别指向数组 A 和 B 的元素。现在关键点来了,我们不能像上题一样让两个指针扫描同一方向,这样会漏掉很多情况,例如一个小值加上一个大值等于目标值。所以需要反向进行扫描,即指针 i 和 j 初始化时分别指向数组 A 的一个元素和数组 B 的最后一个元素。

2.现在开始进行扫描,指针 i 从前往后对数组 A 进行扫描,指针 j 从后往前对数组 B 进行扫描。这其实用到了有序序列的性质,可以保证我们不会漏掉任何一种情况,而扫描时判断的规则如下:

①让指针 i 固定往后走,即每一趟 i 都只往后移一位,然后根据 j 指向的值进行判断。

②如果 A[i]+B[j]>x,说明两指针指向的元素之和大于目标和,现在我们想要这个和更小一点,而指针 j 遍历的方向就是从大到小,故将指针 j 往前移直到元素之和小于等于目标和为止。

③如果此时元素之和等于目标值,则直接输出结果即可。反之,进行下一趟遍历即将指针 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;
}
目录
相关文章
|
4天前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
35 15
|
4天前
|
存储 算法 数据处理
公司局域网管理中的哈希表查找优化 C++ 算法探究
在数字化办公环境中,公司局域网管理至关重要。哈希表作为一种高效的数据结构,通过哈希函数将关键值(如IP地址、账号)映射到数组索引,实现快速的插入、删除与查找操作。例如,在员工登录验证和设备信息管理中,哈希表能显著提升效率,避免传统线性查找的低效问题。本文以C++为例,展示了哈希表在局域网管理中的具体应用,包括设备MAC地址与IP分配的存储与查询,并探讨了优化哈希函数和扩容策略,确保网络管理高效准确。
|
13天前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
20 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
2月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
60 2
|
3月前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
2月前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
3月前
|
算法 容器
【算法】——双指针算法合集(力扣)
移动零,复写零,快乐数,盛最多水的容器,有效三角形的个数,和为s的两个数(查找总价格为目标值的两个商品 ),三数之和,四数之和
|
3月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
121 1
|
4月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
290 4
|
5月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。