【蓝桥杯C/C++】彻底理解双指针算法

学习目标

  1. 了解双指针算法是什么以及分类
  2. 理解双指针算法的原理
  3. 会用代码编写双指针算法
  4. 在实际题目中灵活运用双指针

📌在数组的开章中我们提到了这个算法,如果没有看的话可以学完这一节再回头去看那一篇文章会恍然大悟!!

什么是双指针?

用最通俗的语言来讲所谓双指针算法是针对于遍历的过程的,我们平常在用for循环遍历的时候都是用单个指针在进行循环访问,而双指针就是用两个相同方向的或者相反方向的指针进行扫描,从而达道算法的目的。比如在一层循环里同时设置 i,j两个变量。

换言之,双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算,将时间复杂度从log(n^2)降到log(n)。

注意:这里的指针,并非专指C语言中指针的概念,而是指索引,游标或指针,可迭代对象等!

双指针的分类

  1. 若两个指针指向同一数组,遍历方向相同且不会相交,则称为滑动窗口,用于解决区间搜索问题。
  2. 若两个指针指向同一数组,遍历方向相反,则可以用来搜索有序数组(需要提前进行排序)。
  3. 还有快慢指针,可以用来判断链表环路问题。

核心思想

有重复计算的区间,用双指针优化。将上一个状态指针所表达的信息传递至下一状态,减少无谓的搜索。

模板写法

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑   
   // 这是i,j分别两端的写法

}

//这是i,j分别两端的写法
for(int i=0,j=n-1;i<j;i++){
    while(check()) j--;
}

常见问题分类:

(1) 对于一个序列,用两个指针维护一段区间

(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

常用于解决:数组、区间、字符串等问题

经典例题

移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}
 

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]

解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]

解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100

双指针法

分析题意

首先来分析一下这道题为什么可以使用双指针法,很明显这是一道考察数组的题,我们上面说了数组的题可以考虑用双指针的解法,那么还有其他原因吗?双指针可以代替本来暴力写法的两层循环(不知道暴力学法怎么写的可以参考数组开章的那篇博客),从而减小时间复杂度,答案优化算法的目的。

📌当然我们必须先思考出暴力写法应该如何写,才能想到双指针的优化

具体代码

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slow=0;//双指针  慢指针为长度
        for(int fast=0;fast<nums.size();fast++){
            if(nums[fast]!=val){
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;//返回新的数组的下标 
    }
};

最长连续不重复子序列

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

输入格式

第一行包含整数 n。

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

输出格式

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

数据范围

1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^{5} 1n105

输入样例:

5

1 2 2 3 5

输出样例:

3

核心思路

本题使用i,j两个指针来维护一个区间,如果区间内没有重复的数字就让i指针一直向前移动并且用s[]数组来记录a[]数组中每个元素出现的次数,每出现一个重复的数字的时候就移动j和i重合,重新开始计算区间的长度,最后去更新一个最大的区间长度。

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N],s[N];
int n;
//双指针:找i和j存在什么规律:单调性
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        
    }
    int res=1;
    for(int i=1,j=1;i<=n;i++)
    {
        s[a[i]]++;//注意这一步的位置,向右扩展右端点
         //当扩展完区间右端点之后,有可能这个元素q[i]会有重复,下面这个while循环就是用来去除重复
        //去重只有一个办法,就是收缩区间左端点,同时收缩时要保证j是小于i的
        while(j<i&&s[a[i]]>1)//如果执行这个循环,结束后i与j重合
        {
            
            s[a[j]]--;
            j++;
        } 
        
        res=max(res,i-j+1);
        
    }
    cout<<res<<endl;
    return 0;
}

数组元素的目标和

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

数组下标从 0 开始。

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

数据保证有唯一解。

输入格式

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

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

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

输出格式

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

数据范围

数组长度不超过 10^5
同一数组内元素各不相同。
1≤数组元素≤10^9

输入样例:

4 5 6

1 2 4 7

3 4 6 8 9

输出样例:

1 1

核心思路

i从 0开始 从前往后遍历
j从 m - 1开始 从后向前遍历

📌和纯暴力的O(n2)算法的区别就在于:j指针不会回退

在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m, x;
int a[N], b[N];

int main()
{
    scanf("%d%d%d", &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]);
    //设置前后两个指针  去更新j指针
    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) cout << i << ' ' << j << endl;
    }

    return 0;
}


总结一下

本篇我们学习了双指针算法,可以回头看一下学习目标大家有没有达成了呢~

从例题中可以看到这些题都有一个共性就是都提到了数组这一概念,也就是上一篇数组博客

的内容理解透彻以后再来看这一篇会更加清晰一些😀

创作不易,喜欢的话动动小手点个免费的赞啦,谢谢大家!!~😀💜❤️

在这里插入图片描述

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会喷火的小火龙

你的鼓励是我最大的创作动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值