acwing-双指针算法

双指针

双指针有两个大类,一类是有两个数组,每个数组有一个指针,一类是一个数组,两个指针分别指向头和尾

通用写法

for(i=0,j=0;i<n;i++){
    while(j<i && check(i,j)) j++;
    //每道题目的具体逻辑
}
//核心思想
//对于暴力:
for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
        O(n^2)
//双指针 将上面朴素算法优化到O(n)

核心思想

最长连续不重复子序列

先想朴素做法,再想怎么优化

在这里插入图片描述

放上面红色指针往前移的时候,绿色指针一定不会往后移(不然就矛盾了,因为绿色指针移动逻辑是有重复的元素),因此当红色指针向右移动,绿色指针也一定是向右移动的,具有单调性,所以可以优化每次循环i看j要不要往右走

for(int i=0,j=0;i>n;i++){
 while(j<=i&&check(j,i)) j++;//check判断j-i之间有没有重复元素
    res =max(res,i-j+1);
}

用一个数组动态记录每个数字出现多少次

s[N];

每次i移动一格说明加入了新的数:s[a[i]]++;

每次j移动一格说明要减去a[j]出现的次数s[a[j]]–;

保持s[j,i]区间内每个数字都<=1,如果新加入的a[i+1]是重复的那么s[a[i+1]]>1,需要移动j

核心思路:

  1. 遍历数组a中的每一个元素a[i], 对于每一个i,找到j使得双指针[j, i]维护的是以a[i]结尾的最长连续不重复子序列,长度为i - j + 1, 将这一长度与r的较大者更新给r。
  2. 对于每一个i,如何确定j的位置:由于[j, i - 1]是前一步得到的最长连续不重复子序列,所以如果[j, i]中有重复元素,一定是a[i],因此右移j直到a[i]不重复为止(由于[j, i - 1]已经是前一步的最优解,此时j只可能右移以剔除重复元素a[i],不可能左移增加元素,因此,j具有“单调性”、本题可用双指针降低复杂度)。
  3. 用数组s记录子序列a[j ~ i]中各元素出现次数,遍历过程中对于每一个i有四步操作:cin元素a[i] -> 将a[i]出现次数s[a[i]]加1 -> 若a[i]重复则右移j(s[a[j]]要减1) -> 确定j及更新当前长度i - j + 1给r。

注意细节:

当a[i]重复时,先把a[j]次数减1,再右移j。如果还不懂就拿个例子自己模拟一遍

代码:

import java.util.Scanner;
public class Main {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[] a = new int[100010];
        int[] s = new int[100010];
        for(int i = 0 ; i < n ; i ++ ){
            a[i] = scan.nextInt();
        }
        int res = 0;
        for(int i=0,j=0; i<n; i++){
            s[a[i]]++;//记录放入的数据
            while(s[a[i]]>1){//有两个以上连续的看下一个是不是
                s[a[j]]--;//
                j++;
            }
            res=Math.max(res, i-j +1);
        }
        System.out.println(res);
    }
}

数组元素的目标和

思路

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

和<x就退出j的遍历

代码

import java.util.Scanner;
public class Main {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        int x = scan.nextInt();
        int[] a = new int[100010];
        int[] b = new int[100010];
        for(int i = 0 ; i < n ; i ++ ){
            a[i] = scan.nextInt();
        }
        for(int i = 0 ; i < m ; i ++ ){
            b[i] = scan.nextInt();

        }
        for(int i=0,j=m-1;i<n;i++){
            while(j>=0&&a[i]+b[j]>x) j--;//因为数组是有序的从后到前,小于就可以直接进入下一个i
            if(j>=0&&a[i]+b[j]==x) System.out.printf("%d %d",i,j);
        }
    }
}

判断子序列

思路

每次扫描A,B两个数组判断当前元素是否一样,一样就看后面的元素,一直直到结束了如果A数组走到终点了就存在子序列

上面说明双指针可以找到一种匹配方式

还需要反过来:若存在一种不是双指针的匹配,则上述算法必然可以找出一个匹配。如下图实线代表原匹配,因为双指针可以找到第一个相等的匹配元素,即虚线部分,而不影响其他匹配,则证明前文正确。

在这里插入图片描述

在这里插入图片描述

代码

import java.util.Scanner;
public class Main {
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        int[] a = new int[100010];
        int[] b = new int[100010];
        for(int i = 0 ; i < n ; i ++ ){
            a[i] = scan.nextInt();
        }
        for(int i = 0 ; i < m ; i ++ ){
            b[i] = scan.nextInt();
        }
        int i=0,j=0;
        while (i < n && j < m){
            if(a[i]==b[j]) i++;
            j++;
        }
        if(i==n) System.out.printf("Yes");
        else System.out.printf("No");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值