前言
双指针基于循环,是循环的一种优化,形象理解为可以自控制指针。
由于双指针基于两层循环理解,在开始练习时,可以先写出朴素的两层循环,再进行双指针改写。
双指针算法理解
需要两个可移动的“指针”进行比较,不论是两指针都从头开始,都从尾开始,或者一头一尾,找到了双指针运动含义在解题中满足的某种关系,就可以尝试进行双指针改写,双指针算法是两层循环的优化,具体体现在时间复杂度上。
双层循环(复杂度:O(n^2)):
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) {
复杂度:O(n^2);
}
}
双指针模板:
for(int i = 0,j = 0;i < n;i++) {
while(j < i && check(i,j)) {
j++;//判断是否满足情况,满足情况j++
}
//每道题目具体逻辑
}
双指针和两层循环的区别:
时间复杂度上:
两层循环的时间复杂度为:O(n^2)
双指针的时间复杂度为 :O(n)
练题理解
例【最长连续不重复子序列】
题意理解:
1.起始点:i,j指针都从0开始
2.当[i,j]中没有重复元素时,i++
3.当[i,j]中有重复元素时,j++直到[i,j]中无重复元素
4.用一个变量保存i到j的距离,每一次i、j变化时进行长度统计,保留最长的。
如何判断[i,j]中有无重复元素
数组嵌套记录计数数组:
用list[]保存数组中的数字,再开一个count[]数组记录每个数字出现的次数,i++时count[list[i]]++记录区间中的数出现的次数,当j++时,count[list[j]]–相当于j指针走过的数字不记录进下一个区间出现次数的统计。
题解(Java)
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] list = new int[n];
int[] count = new int[100010];//区间里每个数出现的个数
for(int i = 0;i < n;i++) {
list[i] = sc.nextInt();
}
int res = 0;
for (int i = 0,j = 0; i < n; i++) {
count[list[i]]++;//开计数数组记数组元素的重复个数,标记数组**
while (count[list[i]] > 1){
//有重复的数字
count[list[j]]--;
j++;
}
res = Math.max(res,i-j+1);
}
System.out.println(res);
}
}
例【判断子序列】
朴素写法(核心代码):
int count = 0;
int res = 0;
for(int i = 0;i < n;i++) {
for(int j = count;j < m;j++) {
if(listN[i] == listM[j]) {
res++;
count++;
break;
}else {
count++;
}
}
}
if(res == n) {
System.out.println("Yes");
}else {
System.out.println("No");
}
题意理解:改成双指针
1.定义res来记录有几个相等
2.i从0开始循环子序列数组listN,
3.j一开始从0开始循环数组listM,
4.如果listN[i]和listM[j]相等,i++,j++,res++
5.如果listN[i]和listM[j]不相等,j++
最后判断:如果res和listN数组的长度相等,是子序列;反之,不是子序列。
题解(Java)
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int[] listN = new int[n];
int[] listM = new int[m];
for(int i = 0;i < n;i++) {
listN[i] = sc.nextInt();
}
for(int i = 0;i < m;i++) {
listM[i] = sc.nextInt();
}
int res = 0;
for(int i = 0,j = 0;i < listN.length&&j < listM.length;) {
if(listN[i] == listM[j]) {
i++;
j++;
res++;
}else {
j++;
}
}
if(res == listN.length) {
System.out.println("Yes");
}else {
System.out.println("No");
}
}
}
例【数组元素的目标和】
朴素写法(核心代码):
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if(A[i] + B[j] == x){
System.out.print(i + " " + j);
}
}
}
题意理解:改成双指针
1.因为是升序数组,i是A数组开始的位置,j是B数组结束的位置。
2.如果A[i]+B[j]>x,证明A[i]+B[j]的和要减小,所以j要减小;
3.如果A[i]+B[j]<x,证明A[i]+B[j]的和要变大,所以i要变大;
题解(Java):
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();//A的长度
int m = sc.nextInt();//B的长度
int x = sc.nextInt();//目标值
int[] A = new int[n];
int[] B = new int[m];
for (int i = 0; i < n; i++) {
A[i] = sc.nextInt();
}
for (int i = 0; i < m; i++) {
B[i] = sc.nextInt();
}
int i = 0;
int j = m - 1;
while (i < n && j >= 0){
if(A[i] + B[j] > x){
j--;
}else if(A[i] + B[j] < x){
i++;
}else {
System.out.println(i + " " + j);
break;
}
}
}
}
通过练习可以看到,双指针和两层循环的i,j没有不同,关键是要找到解题中i,j的变化的关系进行判断,再根据不同题意具体分析。刚开始练习时,可以先写成熟悉的两层循环,再看能不能改成双指针,优化时间复杂度。