acwing3

双指针算法
第一类是指向两个序列,例如归并排序
第二类是指向一个序列,例如快排,维护一整个区间
一般写法就一种

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;
		    }
		}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值