双指针算法模板及例题

双指针算法时间复杂度O(n)

一般双指针算法运用于有序的某一个或两个序列中,从O(n2)优化到O(n)

算法模板

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

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作


补充关于字串与子序列的区别:
在这里插入图片描述


第一类双指针:指向两个序列

例:归并排序,acwing2816. 判断子序列,acwing800. 数组元素的目标和
acwing800. 数组元素的目标和
在这里插入图片描述
算法一: 双指针O(n + m)

假定两个指针i指向a数组的初始位置, j指向b数组的终点
由于两个序列都是有序的, 那么如果a[i] + b[j] > x 仅仅回退j指针即可(a[i] + b[j] > x那么i往后移动必然会导致a[i] + b[j]的值会更大只有减小b[j], 才有可能使得a[i] + b[j] <= x), 当a[i] + b[j] > x,那么j就不能再回退了,若是再回退必然会导致a[i] + b[j]更小, 这时只需要不动j, 去将i指针向后移动即可,如此迭代找到a[i] + b[j] == x, 双指针优化时j指针不会每次都再回退到b数组的末尾,从而优化了时间复杂度

code:

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, x;
int a[N], b[N];

int main()
{
    cin >> n >> m >> x;
    for(int i = 0; i < n; i ++ )    cin >> a[i];
    for(int i = 0; i < m; i ++ )    cin >> b[i];
    
    for(int i = 0, j = m - 1; i < n; i ++ )
    {
        //j > 0保证数组不会越界,这里数据保证了一定会有解,加不加都行,感觉还是习惯性还是加上好点
        while(j > 0 && a[i] + b[j] > x) j --;   
        //退出循环a[i] + b[j] <= x,那么直接判断是否等于x,等于输出,不等于就直接将i往后移动
        if(a[i] + b[j] == x)    printf("%d %d\n", i, j);
    }
    
    return 0;
}

算法二:二分O(nlogm)

#include <iostream>
using namespace std;
const int N = 1e5 + 10;

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

int main()
{
    cin >> n >> m >> x;
    for(int i = 0; i < n; i ++ )    cin >> a[i];
    for(int i = 0; i < m; i ++ )    cin >> b[i];
    
    for(int i = 0; i < n; i ++ ) 
    {
    	//二分模板,当然这里也能用lower_bound()
        int l = 0, r = m - 1;
        while(l < r)
        {
            int mid = l + r >> 1;
            if(b[mid] >= x - a[i])  r = mid;
            else l = mid + 1;
            
        }
        if(a[i] + b[l] == x)    cout << i << ' ' << l;
    }
    return 0;
}

acwing2816. 判断子序列
在这里插入图片描述

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++ )    cin >> a[i];
    for(int i = 0; i < m; i ++ )    cin >> b[i];
   
    int i, j;    //i指向b数组,j指向a数组
    /* 
    	注意这里的 j < n 就必须要写了, 若是不写那么当a[]循环完时,
    	那么a[]有效值之后的全是0,此时若是b[]中也是0,j就会错误匹配导致j的值>n
		测试数据:   	a   1
					b	1 	0
	*/
    for(i = 0, j = 0; i < m; i ++)
        if(j < n && a[j] == b[i])    j ++;
      
    if(j == n)  puts("Yes");
    else puts("No");
    
    return 0;
}



第二类双指针:指向一个序列

例:快速排序, acwing799. 最长连续不重复子序列
acwing799. 最长连续不重复子序列
在这里插入图片描述

对于一个序列总要有个快慢指针,外层循环放快指针,内层循环放慢指针
对于本题,用慢指针j,与快指针i进行维护一个区间,不断更新[j, i] 区间的大小,找到最大值。
由于要求是不重复的,所以当发生重复情况,那么一定是快指针i所指向的值与上一个值重复(这里开个数组记录一下每个元素出现的次数,若次数>1说明重复了,若数据较大,可用哈希代替数组)

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N];

int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++ )    cin >> a[i];
    
    int res = 0;
    for(int i = 0, j = 0; i < n; i ++ ) 
    {
        s[a[i]] ++;
        while(j < i && s[a[i]] > 1)     s[a[j ++ ]] --;     
        //若执行了循环,当循环结束后,j刚好指向最后一个i指向的元素,此时i, j 
        res = max(res, i - j + 1);
    }
    cout << res << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值