1、双指针算法
双指针算法:也就是有两个指针i和j,i和j具有一定的单调关系或者存在某种关系,可以使得当j满足什么条件时候,j会往前移动,注意,双指针算法的时间复杂度是O(n),也就是i和j都不会重复扫到一个元素,这里区别于双重循环的O(n^2),在双重循环中,我们的i或者j是有可能一个元素扫描好几次的,一般是n次或者n(n-1)/2或者n(n+1)/2,这里看具体算法了。(许多人认为一层循环嵌套另一层循环,那么时间复杂度就是O(n^2),其实不然,也就是因为这是错误理解,时长分不清双指针和暴力的区别)
接下来我们以两个数组和与最长不重复子序列为例子,详细巩固双指针算法:
1.1、有序数组和
问题: 在给定的一个有序数组中,查找是否存在两个元素的和等于目标值target,这是一个常见的双指针算法,思路日常简单,把i和j指向数组两端,当满足data<target时候i++等等这些属于性质,时间复杂度是O(n);
#include<iostream>
using namespace std;
const int N = 1e6+10;
int a[N];
bool checktarget(int a[],int n,int target){
int i=0,j=n-1;
while(i<j){
int data=a[i]+a[j];
if(data == target){
return true;
}
if(data<target){
i++;
}else{
j++;
}
}
return false;
}
int main(){
int n,m;//n是有序数组长度,m是询问次数
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
int target;
while(m--){
printf("请输入需要寻找的目标值:");
scanf("%d",&target);
if(checktarget(a,n,target))printf("存在");
else printf("不存在");
}
}
1.2、最长不重复子序列
问题:给定一段序列,请你找出最长不重复子序列,例如
1 3 2 2 3 4 5 ->结果:2 3 4 5
最长不重复子序列也是典型的双指针的应用算法,滑动窗口的思路进行。
本题就是典型的双循环嵌套,但是因为i和j存在单调关系,那么最坏时间复杂度也只是O(2n),在全部是一个数的情况下,最长不重复子序列是1,那么i和j会同时在n-1的地方结束,并且期间是不会重复去访问同一个数目的,这就是双指针和暴力的本质区别。
C++实现:
#include <iostream>
using namespace std;
const int N=1e6+10;
int a[N],s[N];
//利用哈希表的思路来做,s[N]把它看做哈希表,也可以自己导入扩展使用哈希表。
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&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滑动到重复的序列那
res=max(res,i-j+1);
}
printf("%d",res);
}
Java实现
import java.util.Scanner;
/*
*
* 最长不重复子序列
*
* */
public class DoublePoint {
public static final int N=100000;
public static final int[] a=new int[N],s = new int[N];
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for(int i=0;i<n;i++){
a[i]=scanner.nextInt();
}
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++]]--;//当录入的值有大于1,也就是出现重复的数目了,把j移动到当前的位置,这里s[a[j++]]--是因为当到达重复序列的时候一直要把重复序列个数减到位置,不然到不了那个位置
res = Math.max(res,i-j+1);
}
System.out.println(res);
}
}