双指针算法
第一类是指向两个序列,例如归并排序
第二类是指向一个序列,例如快排,维护一整个区间
一般写法就一种
for(i=0,j=0;i<n;i++){
while(j<i&&check(i,j)) j++;
//每道题的具体逻辑
}
核心思想:将朴素算法 O(n^2)变成了O(n)
//双指针算法的简单应用
public class test04 {
public static void main(String[] args) {
//输入一个字符串,将其中的每一个单词输出出来,使用空格隔开
String chr="abc def hgk mkl";
char[] ch=chr.toCharArray();
int n=ch.length;
for(int i=0;i<n;i++){
int j=i;
while(j<n&&ch[j]!=' ') j++;
//具体逻辑,一个单词是从何i开始一直到j-1
for(int k=i;k<j;k++){
System.out.print(ch[k]);
}
System.out.println();
i =j;
}
//第一个单词前面没有空格,每个单词之间只需要一个空格隔开
//我们使用双指针是为了使得第一个指针在单词的第一个字母的位置,第二个指针进行遍历后移
}
}
最长连续不重复子序列
给定一个长度为n的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度
这里也是一个双指针算法的应用
算法的过程可以从暴力破解方面入手,暴力做法是一个个枚举,i是终点j是起点每一个可能性都去判断
// fot(int i=0;i<n;i++)
// {
// for(int j=0;j<i;j++)
// {
// if(check(i,j))
// res=max(res,i-j+1);
// }
// }
双指针算法:
j指针最远可以在i指针左边的的哪个位置,且i和j之间没有重复的元素
j指针随着i指针的往后走,是不可能往前走的,因为一旦中间有重复的,指针j前移也一定是有重复的只能后移才可能将重复的去除,或者说,如果i往后走,j往前走,现在i和j之间是误重复元素的,那么上一步的j就不准确了,因为j可以往前移,但是上一步的j就不会是最远的了,矛盾
i j都有单调性
只枚举i就可以
for(int i=0,j=0;i<n;i++)
{
//如果j和i之间有重复元素就让j往后走
while(j<=i&&check(j,i))j++;
res=max(res,i-j+1);
}
数量少的话就可以开一个数组,动态的记录每个数出现多少次s[N],i往后移出现新的数 s[a[i]]++ 是将a[i]的值所在的s[N】的索引位置++
如果j往后移动一格,就相当于数组中有一个数出去s[a[j]]–
以上的方法可以动态统计出来有多少个数
如果i后移,使得有重复的数出现,那么s[a[i]]++的操作会使得a[i]所在位置>1
实际代码:
private static final int N=10010;
public static void main(String[] args){
int[] a=new int[N];
int[] s= new int[N];
Scanner in =new Scanner(new BufferedInputStream(System.in));
int n=in.nextInt();
for(int i=0;i<n;i++) a[i]=in.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=max(res,i-j+1);
}
System.out.println(res);
}
位运算
((n & (n-1))== 0)的含义是n满足2的n次方
n的最高有效位为1,其余位为0。因此,n的值是2的某次方。
所以,(n&(n-1))==0检查n是否为2的某次方(或者检查n是否为0)
最常见的两种位运算
整数n的二进制表示中,第k个数字是几
1)先把第k位移到最后一位,右移到个位
2)看个位是几(现在的个位就是原来的第k位)
for(int k=3;k>=0;k–){
//右移操作中右侧多余的位将会被舍弃,而左侧较为复杂:对于无符号数,会在左侧补 0;而对于有符号数,则会用最高位的数(其实就是符号位,非负数为 0,负数为 1)补齐。
System.out.print(n>>k&1); //将n向右移动k位,左移乘,右移除
这里可以输出二进制形式是因为,n在计算机中是二进制存储的,1010,右移一位101,右移三位就是1,这样将二进制表示出来}
lowbit返回数的最后一位1也就是说x=1010 lowbit(x)返回的是10
lowbit实际上实现的是(x&-x) -x在内存中以补码的形式存在是x取反再加一(x&-x)=(x&(x的反+1)) 在数取反的时候,最后一位1变成0,后面的0都变成1
再加一的话,后面的1都进位变成了0,最后一位1变成了1且不会进位,前面因为取反且相与就全为0
应用是:可以统计1的个数
离散化问题–特指整数离散化
a[]: 1 3 100 2000 500000
离散化: 0 1 2 3 4
在这个过程中可能有一些问题:1)a中可能有重复的元素
2)如何算出x离散化后的值,二分法找x在数组a中的下标
实际上是去重复元素之后,找元素在数组a中的下标
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class test07 {
public static void main(String[] args) {
/*
假定有一个无限长的数轴,数轴上每个左边上的数都是0
首先进行n次操作,每次操作将某一位置x上的数加上c
接下来,进行m次询问,每次询问包含两个整数l和r,需要求出在区间【l,r】之间的所有数的和
当数组下标比较小的时候可以使用前缀和,将数插进入,拆分成前缀和形式
现在的情况是:数的范围很大,但是很稀疏,用离散化做
下标是映射的值
*/
Scanner in = new Scanner(new BufferedInputStream(System.in));
int n=in.nextInt();
int m=in.nextInt();
List<Integer> alls = new ArrayList<Integer>();//存放组成前缀和的元素
List<Pair> add=new ArrayList<>();//存放插入的数据
List<Pair> query = new ArrayList<>();//存放询问的l和r
int[] ori=new int[300010];
int[] sum = new int[300010];//前缀和
//读取数据
for(int i=0;i<n;i++)
{
int a=in.nextInt();
int b=in.nextInt();
add.add(new Pair(a,b));//添加新的Pair对象到add表中,这里是添加的插入的位置和插入的数值在下标a的位置上加上b
alls.add(a);//数值a是需要离散化的
}
for(int j = 0; j < m; j++){
int l = in.nextInt();
int r = in.nextInt();
query.add(new Pair(l, r));//这里是m次询问的时候求l到r上的区间和,读取左右区间
alls.add(l);//l和r这两个左右区间也是需要离散化的
alls.add(r);
}
//排序去重
Collections.sort(alls);
int unique = unique(alls);
alls = alls.subList(0, unique);//sublist截取并且返回动态数组中的一部分,有重复的部分偶放到了数组的后面,所以只留下前面的一部分作为新的alls
//插入操作
for(Pair item : add){
int tag = find(item.first, alls);//这里的first是我们定义的类Pair中的属性,tag离散化之后的索引的值
ori[tag] += item.second;//在离散化之后的位置上加上要加的数据
}
//前缀和
for(int i = 1; i <= alls.size(); i++) sum[i] = sum[i - 1] + ori[i];
//询问处理
for(Pair item : query){
int l = find(item.first, alls);
int r = find(item.second, alls);
System.out.println(sum[r] - sum[l - 1]);
}
}
//实际上也是一个双指针算法,我们要保证他和前面的元素不一样(已经排序过了),且是第一次出现的
public static int unique(List<Integer> list){
int j = 0;
for(int i = 0; i < list.size(); i++){
if(i == 0 || list.get(i) != list.get(i - 1)){
list.set(j, list.get(i));
j++;
}
}
return j;
}
//二分法查找元素在数组中的位置,使用第一个模板,因为是映射从1开始的自然数,所以返回的l要加1,l是从0开始的
public static int find(int x, List<Integer> list){
int l = 0;
int r = list.size() - 1;
while(l < r){
int mid = l + r >> 1;
if(list.get(mid) >= x){//我们找的是》=x最小的数
r = mid;//说明是用的第一个模板所以说l+r>>1不需要加一个1
}else{
l = mid + 1;
}
}
return l + 1;
}
}
class Pair{
int first;
int second;
public Pair(int first, int second) {
this.first = first;
this.second = second;
}
}