双指针概念
所谓双指针,并不是两个指针,而是两个表示下标的变量。我们根据条件对这两个指针进行迭代。一般分两种用法:对撞指针、快慢指针。
双指针模版
对撞指针
使用两个指针left,right,分别指向第一个与最后一个元素。
满足一定条件时左指针右移,满足另外条件时右指针左移,直至两指针相撞(left=right),退出循环。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = s.next();
char []c = str.toCharArray();
boolean is = true;
for(int i = 0,j = c.length-1;i<j;i++,j--){
if(c[i]!=c[j]){
is = false;
break;
}
}
if(is)
System.out.println("Y");
else
System.out.println("N");
}
}
在这道例题中,分别用左右指针表示首尾两字符,如果两字符相同,左右指针同时向中间移动。直至二者对撞都相等,则判定为true。
快慢指针
两指针放在同一侧(不一定都是第一个元素),并且一个移动快,一个移动慢。
假设慢指针为l,快指针为r,二者构成区间[l,r]。
直至快指针达到终点,或快慢指针相交,或满足其他条件时退出循环。
int l = 0;
int r = 0;
while(r<n){
sum += a[r];
while(sum>=S){
max=Math.min(max,r-l+1);
sum-=a[l];
l++;
}//满足sum>S时l右移,否则仅r右移//
r++;
}
在这道例题中,我们设置l,r的初始值均为0。r始终右移,当sum>S时,若仅r继续右移必然使区间长度增大,这时使l右移。其余情况下仅r右移。
由于快慢指针的难度更大,再给出一道例题。
int l = 0;
int r = k-1;
int count;//记录区间内大于等于m的数字个数
while(r<n){
count = 0;
for(int i = l; i <= r; i ++){
if (a[i] >= m) {
count ++;
}
}
if(count==k){
l++;
ans += n-r;
r=l+k-2;
}
r ++;
}
这道例题中,由于子串带有区间属性,我们将r初始化为k-1,使得[l,r]区间的长度至少等于最小子串长度。
r始终右移,当区间内count==k时,后面的子串一定都满足条件,这时使l++,否则仅r++。在这道题中还需要注意l自增后,r的位置需要更新为新l的最小子串的右端点,防漏。又因为最后r会自增,所以将r更新为右端点前一位。
双指针变题
Arrays.sort(a);
long ans = 0;
for(int j = 0,k = 0,i = 0; i < n; i ++){
while(j<n-1&&a[j]-a[i]<C){
j++;
}
while(k<n&&a[k]-a[i]<=C){
k++;
}
if(a[k-1]-a[i]==C&&a[j]-a[i]==C){
ans+=k-j;
}
}
首先,面对无序的数组,很难有条理地计算出差值的组合,而题目对于顺序没有要求,只需要统计个数。因此我们可以对原数组进行排序(O(nlogn))。
这一题使用单纯的双指针很难解决,难点有二:
(1):不论是l还是r右移,差值C总是不严格单调增加。这也就导致使用传统双指针必然存在漏答案的可能,因为在差值小于C的情况下,l右移或者r右移均可能成立;差值大于C时,只能使l左移或者r左移,也均可能成立。
(2):可能存在同数字区间,例如连续的4,可能会漏。
因此,我们使用三个变量来表示数字的下标。使用[j,k]区间表示同数字区间。其中有几个小细节:
(1):为什么j与k的while判断条件有细微的差别->k取到n以及a[k]-a[i]大于C都是为了使得k处于j的右侧。当j为n-1时,k取n,才能使k-j=1。
(2):ans的数据类型设定为long。->n的范围为2e5,由排列组合可知最多存在2e10左右的答案,而int型的最大值只到2e9,因此使用long型才能得到全部的分数。