算法--双指针

算法基础系列


概念

所谓双指针算法,就是指的是在遍历的过程中,不是普通的使用单个指针进行循环访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的

双指针的形式有很多,但核心思想是降低时间复杂度

例如:对于 i , j i,j i,j 的双重循环,时间复杂度是 O ( n 2 ) O(n^2) O(n2),而运用了双指针算法优化之后的时间复杂度是 O ( n ) O(n) O(n)

优化的本质是找 i i i j j j 的变化规律——单调性

一般做法:

  1. 先想一个朴素(暴力)做法,时间复杂度是 O ( n 2 ) O(n^2) O(n2)
  2. 找规律,看看题目有什么规律
  3. 优化

模板代码

for(int i=0,j=0;i<n;i++)
{
    while(j<i && check(i,j)) j++;
    //每道题目的具体逻辑
}

最简单的模板运用:输入一个字符串,输出每个单词(以空格隔开)
建议画图理解

int n = strlen(str);
for(int i=0;i<n;i++)
{
    int j=i;//第二个指针
    while(j<n && str[j] != ' ') j++;

    //这道问题的具体逻辑
    for(int k=i;k<j;k++) cout<<str[k];
    cout<<endl;
    i=j;//i 等于了j 的位置,也就是空格位置,然后再 i ++,指向了下一个单词的首字母
}

练习题

最长连续不重复子序列 – 模板题

传送门
在这里插入图片描述

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, res;
int a[N];
int st[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);

    for (int i = 0, j = 0; i < n; i++)
    {
        st[a[i]]++; //向右扩展区间右端点
        //当扩展完区间右端点之后,有可能这个元素q[i]会有重复,下面这个while循环就是用来去除重复
        //去重只有一个办法,就是收缩区间左端点,同时收缩时要保证j是小于i的
        while (j < i && st[a[i]] > 1)
            st[a[j++]]--;//区间数量减一,之后区间断点右移
        res = max(res, i - j + 1);
    }

    printf("%d", res);
    return 0;
}

数组元素的目标和

传送门
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

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

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    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] > k)
            j--;
        if(j >= 0 && a[i] + b[j] == k)
            printf("%d %d", i, j);
    }
    return 0;
}

判断子序列

传送门
在这里插入图片描述

#include <algorithm>
#include <cstdio>
#include <iostream>
using namespace std;
// 新增的双指针模板题
// 基本思路就是遍历一遍“子序列” ,
// 看看其每一个元素另一个数组中是不是在都有各自的下标
// 且下标要呈递增趋势,这就有了单调性 可以用双指针
int main()
{
    int n, m, i, j;
    scanf("%d%d", &n, &m);
    int a[n], b[m];
    for (i = 0; i < n; i++)
        scanf("%d", &a[i]);
    for (j = 0; j < m; j++)
        scanf("%d", &b[j]);
    i = 0, j = 0;
    //记得复原下ij
    while (i < n && j < m)
    {
        if (a[i] == b[j])
            i++;
        j++;
        //这个循环可理解为 对于a[i]
        // b从j开始遍历 直到a[i] =b[j]了,让i+=1
        //对于新的a[i],重复上行过程
        //如此一来实质上就是以a[i]是否=b[j] 遍历了一遍b,注意是一遍
        //期间若a[i] == b[j]了 更新a[i](即i++)继续遍历
    }
    if (i == n)
        printf("Yes\n");
    //从上面的循环出来了 若a[i]遍历完了(i == n)
    //就说明a是子序列 反之就不是
    else
        printf("No"); 
    return 0;
}

日志统计

传送门
在这里插入图片描述
在这里插入图片描述
按订单排序

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1e5+10;

int n, d, k;

PII logs[N];//记录日志
int cnt[N];
bool st[N];// 记录是否是热帖

int main()
{
    scanf("%d%d%d", &n, &d, &k);
    for (int i = 0; i < n; i ++)
        scanf("%d%d",&logs[i].x,&logs[i].y);
    sort(logs, logs + n);
    for (int i = 0,j = 0; i < n; i++)//枚举订单
    {
        int id = logs[i].y;
        cnt[id] ++;
        while(logs[i].x - logs[j].x >= d )
        {
            cnt[logs[j].y]--;
            j++;
        }
        if(cnt[id] >= k)
            st[id] = true;
    }
    for (int i = 0; i <= N; i ++)
        if(st[i])
            printf("%d\n", i);
    return 0;
}

完全二叉树的权值

传送门

在这里插入图片描述
在这里插入图片描述
理解完全二叉树的定义

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
int a[N];

int main()
{
    scanf("%d", &n);

    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);

    /*
    完全二叉树 每层的开头为   2^(n-1) 结尾则是 2^n - 1
    计算每层的数值只需要两个positioner 分别指向开头和结尾
    */
    
    LL maxv = -1e18;
    int depth = 0;
    for (int i = 1, d = 1; i <= n; i *= 2,d++)
    {
        LL s = 0;
        for (int j = i; j < i + (1 << d - 1) && j <= n; j++)
            s += a[j];

        if (s > maxv)
        {
            maxv = s;
            depth = d;
        }
    }
    cout << depth << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Tancy.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值