(一)基础算法

目录

(一)基础算法

1、快速排序

例题1:第k个数

2、归并排序

例题一:逆序对的数量

3、二分

例题一:数的范围

例题二:数的三次方根

4、前缀和与差分

例题一:前缀和

例题二:子矩阵和

例题三:差分

例题四:差分矩阵

5、双指针算法

例题一:最长连续不重复子序列

例题二:数组元素的目标和

例题三:判断子序列

6、位运算

例题一:二进制中一的个数

7、离散化

例题一:区间和

8、区间合并

例题一:区间合并


1、快速排序

(158条消息) 什么?一篇理解排序算法(下)_yan扬的博客-CSDN博客https://blog.csdn.net/qq_59539549/article/details/123609047?spm=1001.2014.3001.5501

由于之前的博客已经详细快排和归并的实现原理和逻辑 这里不再赘述 仅给出实现代码和例题

快排代码实现(左右指针)

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);//读取数据(数组长度)int n=sc.nextInt();int[] nums=new int[n];//接受数据的数组for(int i=0;i<n;i++){nums[i]=sc.nextInt();//给数组赋值}quickSort(nums,0,n-1);//对数组下标 0~n-1 进行排序for(int i=0;i<n;i++){System.out.print(nums[i]+" ");}}public static void quickSort(int[] nums,int st,int ed){if(st>=ed) return;//一个数时默认有序int l=st;//定义左右指针int r=ed;int temp=nums[l];//privot元素 大于等于该值放右边 小于等于放左边while(l<r){while(l<r&&nums[r]>=temp){r--;      //在右边找比temp小的元素}while(l<r&&nums[l]<=temp){l++;     //在左边找严格大temp的元素}swap(nums,l,r);}swap(nums,st,l);//让temp处于在自己该待的位置quickSort(nums,st,l);quickSort(nums,l+1,ed);}public static void swap(int[] nums,int i,int j){  //交换函数int t=nums[i];nums[i]=nums[j];nums[j]=t;}
}

这里补充一点:递归左边进行快排时 右边界 是 l 和 l-1 其实都是可以的 因为我们选作最左边的元素作为privot作为temp 不会出现死循环 因为每次调用最起码要去掉一个

例题1:第k个数

给定一个长度为 nn 的整数数列,以及一个整数 kk,请用快速选择算法求出数列从小到大排序后的第 kk 个数。

输入格式

第一行包含两个整数 nn 和 kk。

第二行包含 nn 个整数(所有整数均在 1∼1091∼109 范围内),表示整数数列。

输出格式

输出一个整数,表示数列的第 kk 小数。

数据范围

1≤n≤1000001≤n≤100000,
1≤k≤n1≤k≤n

输入样例:

5 3
2 4 1 5 3

输出样例:

3

思路:快排的思路 我们每次可以确定一个值 左边的数都比他小 右边的数都比他大 因此我们一定知道当前这个数是 第几大” 的数 因此我们每次递归只需要选择一侧去递归 如果k<=左侧长度 我们就递归左边 否则递归右边 (但要记得 k要变为 k- 左侧长度 )

代码实现:

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int k=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}int res=quickCheck(nums,0,n-1,k);System.out.print(res);}public static int quickCheck(int[] nums,int st,int ed,int k){if(st==ed){return nums[st];//就是我们要找到的}int l=st;int r=ed;int temp=nums[st];while(l<r){while(l<r&&nums[r]>=temp){r--;}while(l<r&&nums[l]<=temp){l++;}swap(nums,l,r);}swap(nums,st,l);int len=l-st+1;//计算左侧长度 判断k在左还是右if(k<=len){return quickCheck(nums,st,l,k);}else{return quickCheck(nums,l+1,ed,k-len);//记得传进去的是 k-len}}public static void swap(int[] nums,int i,int j){int t=nums[i];nums[i]=nums[j];nums[j]=t;}
}

2、归并排序

同理:之前写过相关原理 这里仅给出代码

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}mergeSort(nums,0,n-1);for(int i=0;i<n;i++){System.out.print(nums[i]+" ");}}public static void mergeSort(int[] nums,int st,int ed){if(st==ed) return;//一个元素时默认有序int mid=st+ed>>1;mergeSort(nums,st,mid);//把数组分割成小数组 再合并两个有序的小数组mergeSort(nums,mid+1,ed);int p1=st;int p2=mid+1;int k=0;//辅助数组合并时的下标int[] help=new int[ed-st+1];//辅助数组while(p1<=mid&&p2<=ed){if(nums[p1]<=nums[p2]){help[k++]=nums[p1++];}else{help[k++]=nums[p2++];}}while(p1<=mid){help[k++]=nums[p1++];}while(p2<=ed){help[k++]=nums[p2++];}for(int i=0;i<k;i++){nums[st+i]=help[i];}}
}

例题一:逆序对的数量

给定一个长度为 nn 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 ii 个和第 jj 个元素,如果满足 i<ji<j 且 a[i]>a[j]a[i]>a[j],则其为一个逆序对;否则不是。

输入格式

第一行包含整数 nn,表示数列的长度。

第二行包含 nn 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1≤n≤1000001≤n≤100000,
数列中的元素的取值范围 [1,109][1,109]。

输入样例:

6
2 3 4 5 6 1

输出样例:

5

核心思路:依然是把数组先拆分成小数组,我们总的逆序对数量=左段逆序对数+右端逆序对数+合并时产生的逆序对数(p1指针后的所有数都大于当前数)

易错点:在合并时 如果遇到两个数组中值相同 我们一定要左侧的先放进辅助数组 如果右侧的先放 可能会导致 左侧后边还有比当前值大的数,但我们这个数已经放进辅助数组被丢弃了 也就是有逆序对没有被计算   另一个易错点是需要用long类型 否则int会溢出

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}long res=mergeSum(nums,0,n-1);System.out.print(res);}public static long mergeSum(int[] nums,int st,int ed){if(st==ed) return 0;//分成只含一个元素的数组 自然没有逆序对int mid=st+ed>>1;long left=mergeSum(nums,st,mid);long right=mergeSum(nums,mid+1,ed);int[] help=new int[ed-st+1];int p1=st;int p2=mid+1;int k=0;long res=left+right;while(p1<=mid&&p2<=ed){if(nums[p1]<=nums[p2]){help[k++]=nums[p1++];}else{help[k++]=nums[p2++];res+=mid-p1+1;}}while(p1<=mid){help[k++]=nums[p1++];}while(p2<=ed){help[k++]=nums[p2++];}for(int i=0;i<k;i++){nums[st+i]=help[i];}return res;}
}

3、二分

二分常被用来寻找一段拥有单调性的区间边界

比如:

在这段区间里 我们想寻找最靠近绿色的红色下标 只需要控制我们的查找值永远处于我们的l到r之间

不难写出以下伪代码

//首先二分肯定是需要在一段区间上进行二分
int l;
int r;
int mid=l+r>>1;
if(check(nums[mid])==red){//如果满足红色区间 我们缩小边界l=mid;    //为什么是l等于mid  因为我们要保证这个最接近绿色的红色边界永远处于我们的l到r之间
}else{r=mid-1;
}

但此时还有一个问题 就是说假如 l=r-1  ,由于mid存在向下取整  当l=mid 后 第二次 的循环依旧是 mid=l 所以查找区间还是原来的 [l,r] 会造成死循环 因此我们在一开始的mid求值时 需要改为 l+r+1>>1

反之如果我们寻找绿的的最靠左边界 mid求值时把r改为mid 不会造成死循环

例题一:数的范围

给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。

对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 nn 和 qq,表示数组长度和询问个数。

第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。

接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。

输出格式

共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

思路:先对数组排序 分别求>=k 的最小值 以及<=k的最大值即可

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int q=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}while(q-->0){int k=sc.nextInt();int l=0;int r=n-1;while(l<r){int mid=r+l>>1;if(nums[mid]>=k){r=mid;}else{l=mid+1;}}if(nums[l]==k){System.out.print(l+" ");l=0;r=n-1;while(l<r){int mid=l+r+1>>1;if(nums[mid]<=k){l=mid;}else{r=mid-1;}}System.out.print(l);//上一个找的到 这个就一定找得到}else{System.out.printf("%d %d",-1,-1);}System.out.println();}}
}

例题二:数的三次方根

给定一个浮点数 nn,求它的三次方根。

输入格式

共一行,包含一个浮点数 nn。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 66 位小数。

数据范围

−10000≤n≤10000−10000≤n≤10000

输入样例:

1000.00

输出样例:

10.000000

思路:由于浮点数二分不存在取整问题 因此每次的范围必定缩小一半 我们只需要控制 当l和r之间的差值要多小的时候将它看作一个数即可

易错点:浮点数求mid时不能使用l+r>>1  必须使用(l+r)/2

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);double n=sc.nextDouble();double l=-10000;double r=10000;while(r-l>1e-8){//精度通常比题目高两位double mid=(l+r)/2;if(mid*mid*mid>=n){r=mid;}else{l=mid;}}System.out.printf("%.6f",l);}
}

4、前缀和与差分

例题一:前缀和

前缀和 顾名思义 假设有一个数组a 那么他的前缀和数组 b 中任一元素 b【i】就表示 a0+a1+...+a[i-1]+a[i]

前缀和的作用:如果我们要求a数组中某一段数组的和 我们就可以用o(1)的复杂度求出

假设要求a中 l到 r 范围的和 我们只需计算 b[r]-b[l-1]

输入一个长度为 nn 的整数序列。

接下来再输入 mm 个询问,每个询问输入一对 l,rl,r。

对于每个询问,输出原序列中从第 ll 个数到第 rr 个数的和。

输入格式

第一行包含两个整数 nn 和 mm。

第二行包含 nn 个整数,表示整数数列。

接下来 mm 行,每行包含两个整数 ll 和 rr,表示一个询问的区间范围。

输出格式

共 mm 行,每行输出一个询问的结果。

数据范围

1≤l≤r≤n1≤l≤r≤n,
1≤n,m≤1000001≤n,m≤100000,
−1000≤数列中元素的值≤1000−1000≤数列中元素的值≤1000

输入样例:

5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例:

3
6
10
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();//m个询问int[] a=new int[n+1];//前缀和数组 为什么数组长度多开了一个1 int[] b=new int[n+1];//因为我们a[i]=a[i-1]+b[i] 多开一个1不需要对0特殊处理for(int i=1;i<=n;i++){b[i]=sc.nextInt();}for(int i=1;i<=n;i++){a[i]=a[i-1]+b[i];}while(m-->0){int l=sc.nextInt();int r=sc.nextInt();System.out.println(a[r]-a[l-1]);}}                   }

例题二:子矩阵和

输入一个 nn 行 mm 列的整数矩阵,再输入 qq 个询问,每个询问包含四个整数 x1,y1,x2,y2x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。

对于每个询问输出子矩阵中所有数的和。

输入格式

第一行包含三个整数 n,m,qn,m,q。

接下来 nn 行,每行包含 mm 个整数,表示整数矩阵。

接下来 qq 行,每行包含四个整数 x1,y1,x2,y2x1,y1,x2,y2,表示一组询问。

输出格式

共 qq 行,每行输出一个询问的结果。

数据范围

1≤n,m≤10001≤n,m≤1000,
1≤q≤2000001≤q≤200000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21

思路:本质上就是二维的前缀和 只需要更改求前缀和的公式 此时a[i][j] 代表的是b中i,j左上部分所有的和  因此更改 前缀和计算公式为 a[i-1][j]+a[i][j-1]-a[i-1][j-1]+b[i][j]即可 而我们要求子矩阵的和 就是 a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]

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 q=sc.nextInt();int[][] b=new int[n+1][m+1];int[][] a=new int[n+1][m+1];//前缀和数组for(int i=1;i<=n;i++){  //接受数据for(int j=1;j<=m;j++){b[i][j]=sc.nextInt();}}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+b[i][j];//二维前缀和求和公式}}while(q-->0){int x1=sc.nextInt();int y1=sc.nextInt();int x2=sc.nextInt();int y2=sc.nextInt();System.out.println(a[x2][y2]-a[x2][y1-1]-a[x1-1][y2]+a[x1-1][y1-1]);}}
}

例题三:差分

差分:假设a数组是b数组的前缀和数组 那么b就是a的差分 b【i】=a【i】-a【i-1】

差分的用处:假设我们要给a数组 l~r 范围都加一个整数c 正常来说我们必须要依次把 l~r 范围内依次加c 但如果我们有差分数组 我们只要更改 b【i】+=c 由于a是b的前缀和 a中i 以后的元素都会多加一个c 我们再把b【r+1】-=c 那么 r+1之后的数又会少一个c 以此实现 i~r 范围内数据的加值运算

易错点:首先要明白 a数组并不会随着b数组的改变而改变 因为即使有前缀和的定义 当b数组发生变化是 a数组是要重新求一编前缀和的  有人可能会问:那我要这差分数组有什么用 ?

差分的作用体现在多次询问时 我们只需要改变b这个差分数组多次 然后最后只求一次前缀和即可!

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[] b=new int[n+2];//由于插入时涉及到 r+1 所以我们要多留一个空间 以防止数组越界for(int i=1;i<=n;i++){//我们没有必要可以构建差分数组 采用插入的方式构建差分数组int c=sc.nextInt();insert(b,i,i,c);}while(m-->0){int l=sc.nextInt();int r=sc.nextInt();int c=sc.nextInt();insert(b,l,r,c);}for(int i=1;i<=n;i++){b[i]+=b[i-1];System.out.print(b[i]+" ");}}public static void insert(int[] b,int l,int r,int c){b[l]+=c;b[r+1]-=c;}
}

三个小tips:

1、开数组多开了一个 因为我们要用到b【r+1】+=c 这部操作 防止数组越界

2、其实我们没有必要去构造b差分数组 想象一开始a数组中全为0 我们通过不断插入 i~i 下表内 +a【i】

3、最后求前缀和的时候其实有一步骤不容易看出 就是 b[i]+=b[i-1]; 为什么这一步可以求前缀和 而不是 b0+b1+。。。+b【i】 因为在我们i-1的步骤里 b【i-1】的定义已经变成前缀和了

因此我们求b【i】(前缀和)时只需要加上原来的b【i】(差分数组)即可

例题四:差分矩阵

输入一个 n 行 m 列的整数矩阵,再输入 qq 个操作,每个操作包含五个整数 x1,y1,x2,y2,cx1,y1,x2,y2,c,其中 (x1,y1)(x1,y1) 和 (x2,y2)(x2,y2) 表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上 cc。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数 n,m,qn,m,q。

接下来 nn 行,每行包含 mm 个整数,表示整数矩阵。

接下来 qq 行,每行包含 55 个整数 x1,y1,x2,y2,cx1,y1,x2,y2,c,表示一个操作。

输出格式

共 nn 行,每行 mm 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围

1≤n,m≤10001≤n,m≤1000,
1≤q≤1000001≤q≤100000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤y1≤y2≤m,
−1000≤c≤1000−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2

很明显 这道题要求我们把矩阵中的数字都加c 很容易想得到差分 但是对一个子矩阵中的值+c 应该如何计算呢?  首先我们构建差分矩阵  b[x1][y1]+c 那么它的右下部分都将加上一个c 因此 我们需要 把 b[x2+1][y1]-c     b[x1][y2+1]-c      b[x2+1][y2+1]+c

那求前缀和公式呢?

b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+b[i][j]

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 q=sc.nextInt();int[][] b=new int[n+2][m+2];for(int i=1;i<=n;i++){   //构建差分数组for(int j=1;j<=m;j++){int c=sc.nextInt();insert(b,i,j,i,j,c);}}while(q-->0){int x1=sc.nextInt();int y1=sc.nextInt();int x2=sc.nextInt();int y2=sc.nextInt();int c=sc.nextInt();insert(b,x1,y1,x2,y2,c);}for(int i=1;i<=n;i++){      //求前缀和for(int j=1;j<=m;j++){b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];}}for(int i=1;i<=n;i++){      //输出矩阵for(int j=1;j<=m;j++){System.out.print(b[i][j]+" ");}System.out.println();}}public static void insert(int[][] b,int x1,int y1,int x2,int y2,int c){b[x1][y1]+=c;b[x1][y2+1]-=c;b[x2+1][y1]-=c;b[x2+1][y2+1]+=c;}
}

5、双指针算法

双指针应该是我们很常见的一种算法,一般分为两种

一种是在两个数组中两个指针移动,例如我们在归并中用到过的合并两个有序数组

另一种是在同一个区间 ,根据两个指针的前后关系来完成某些逻辑

例题一:最长连续不重复子序列

给定一个长度为 nn 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 nn。

第二行包含 nn 个整数(均在 0∼1050∼105 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围

1≤n≤1051≤n≤105

输入样例:

5
1 2 2 3 5

输出样例:

3

思路:我们维持两个指针 i 和 j ,使得我们目前的不重复连续子序列永远处于 i 到 j ,如果 我们当前区间没有重复元素就扩充i,否则就需要通过移动 j 来实现“去重”,每次移动 i 时 都要判断一次当前长度是不是最大长度的连续不重复子序列  最后返回最大的长度即可

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}               int[] a=new int[100010];//这里采用一种“桶”去重int j=0;//以 i 结尾 以 j 开头 是我们要计算的不重复子序列的长度int res=-1;for(int i=0;i<n;i++){a[nums[i]]++;  //桶里对应的nums【i】++ 表示这个数 在我们当前序列至少存在一个了while(a[nums[i]]>1){  //由于我们维持的是一个不重复序列 如果产生了重复 那一定是因为i的加入a[nums[j]]--; //去掉最后面的数 想要维持不重复的子区间 直到找到 “内鬼”j++;}res=Math.max(res,i-j+1);}System.out.print(res);}
}

tips:这里采用了一种“桶”的方式去重 当然 我们也可以用hashmap 记录当前元素上一次出现的下标 然后j在此位置上+1即可

例题二:数组元素的目标和

给定两个升序排序的有序数组 AA 和 BB,以及一个目标值 xx。

数组下标从 00 开始。

请你求出满足 A[i]+B[j]=xA[i]+B[j]=x 的数对 (i,j)(i,j)。

数据保证有唯一解。

输入格式

第一行包含三个整数 n,m,xn,m,x,分别表示 AA 的长度,BB 的长度以及目标值 xx。

第二行包含 nn 个整数,表示数组 AA。

第三行包含 mm 个整数,表示数组 BB。

输出格式

共一行,包含两个整数 ii 和 jj。

数据范围

数组长度不超过 105105。
同一数组内元素各不相同。
1≤数组元素≤1091≤数组元素≤109

输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1

思路:首先题目说明两个数组已经有序 我们创建两个指针 i 和 j  ,i 指向第一个数组的开头

j 指向第二个数组的末尾 , 每次都计算 nums1【i】+nums2【j】 如果大于x 那么 j 肯定得 --

因为i后边的元素都是大于nums1【i】的,所以不可能凑出x 如果小于x 那么 x 肯定得++ 因为j 已经是最大的了 要想更大 只能通过移动 i !

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[] nums1=new int[n+1];int[] nums2=new int[m+1];int x=sc.nextInt();for(int i=0;i<n;i++){nums1[i]=sc.nextInt();}for(int i=0;i<m;i++){nums2[i]=sc.nextInt();}int j=m-1;for(int i=0;i<n;i++){while(nums1[i]+nums2[j]>x){j--;}if(nums1[i]+nums2[j]==x){System.out.print(i+" "+j);break;}}}
}

例题三:判断子序列

给定一个长度为 nn 的整数序列 a1,a2,…,ana1,a2,…,an 以及一个长度为 mm 的整数序列 b1,b2,…,bmb1,b2,…,bm。

请你判断 aa 序列是否为 bb 序列的子序列。

子序列指序列的一部分项按原有次序排列而得的序列,例如序列 {a1,a3,a5}{a1,a3,a5} 是序列 {a1,a2,a3,a4,a5}{a1,a2,a3,a4,a5} 的一个子序列。

输入格式

第一行包含两个整数 n,mn,m。

第二行包含 nn 个整数,表示 a1,a2,…,ana1,a2,…,an。

第三行包含 mm 个整数,表示 b1,b2,…,bmb1,b2,…,bm。

输出格式

如果 aa 序列是 bb 序列的子序列,输出一行 Yes

否则,输出 No

数据范围

1≤n≤m≤1051≤n≤m≤105,
−109≤ai,bi≤109−109≤ai,bi≤109

输入样例:

3 5
1 3 5
1 2 3 4 5

输出样例:

Yes

思路:很简单的一道题 定义两个指针分别指向 数组一 和 数组二 如果nums【i】==nums【j】如果匹配完了所有的 nums1中的元素 也就是i 成功走到了最后 则说明找到了返回yes 如果没找到 返回no即可

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[] nums1=new int[n];int[] nums2=new int[m];for(int i=0;i<n;i++){nums1[i]=sc.nextInt();}for(int j=0;j<m;j++){nums2[j]=sc.nextInt();}int i=0;for(int j=0;j<m;j++){if(i>=n) break;if(nums1[i]==nums2[j]) i++;}if(i==n) {System.out.print("Yes");}else{System.out.print("No");}}
}

tips:双指针是一种很巧妙的方法,他能把时间复杂度O(N^2) 变为O(N)

为什么for循环里有个while 还是O(n)的时间复杂度呢?  其实我们换个角度分析 每个数i 和j 都只经过一次 所以总的来说计算次数应该是 2n 时间复杂度自然是 O(N)

6、位运算

这里只介绍两种运算:

1、计算二进制第k位  只需把元素>> k位  再&1 如果他是0 得到的就是0 如果是1 得到的就是1

2、获取二进制最后一位1 首先我们要明白整数取他的负数 二进制代码其实是反码+1,那么我们通过 x&-x就能得到最后一位1所对应的整数

例题一:二进制中一的个数

给定一个长度为 nn 的数列,请你求出数列中每个数的二进制表示中 11 的个数。

输入格式

第一行包含整数 nn。

第二行包含 nn 个整数,表示整个数列。

输出格式

共一行,包含 nn 个整数,其中的第 ii 个数表示数列中的第 ii 个数的二进制表示中 11 的个数。

数据范围

1≤n≤1000001≤n≤100000,
0≤数列中元素的值≤1090≤数列中元素的值≤109

输入样例:

5
1 2 3 4 5

输出样例:

1 1 2 1 2

思路:上边我们讲过获取最后一位1对应的整数 的方式 因此我们可以通过 不断地获取最后一位1 然后再让x-=这个获取到的数 直到它为0  我们只需要记录次数即可

import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);int n=sc.nextInt();int[] nums=new int[n];for(int i=0;i<n;i++){nums[i]=sc.nextInt();}for(int i=0;i<n;i++){int res=0;while(nums[i]!=0){nums[i]-=nums[i]&-nums[i];res++;}System.out.print(res+" ");}}
}

7、离散化

离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。

通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:

原数据:1,999,100000,15;处理后:1,3,4,2;

原数据:{100,200},{20,50000},{1,400};

处理后:{3,4},{2,6},{1,5};

例题一:区间和

假定有一个无限长的数轴,数轴上每个坐标上的数都是 00。

现在,我们首先进行 nn 次操作,每次操作将某一位置 xx 上的数加 cc。

接下来,进行 mm 次询问,每个询问包含两个整数 ll 和 rr,你需要求出在区间 [l,r][l,r] 之间的所有数的和。

输入格式

第一行包含两个整数 nn 和 mm。

接下来 nn 行,每行包含两个整数 xx 和 cc。

再接下来 mm 行,每行包含两个整数 ll 和 rr。

输出格式

共 mm 行,每行输出一个询问中所求的区间内数字和。

数据范围

−109≤x≤109−109≤x≤109,
1≤n,m≤1051≤n,m≤105,
−109≤l≤r≤109−109≤l≤r≤109,
−10000≤c≤10000−10000≤c≤10000

输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5

思路:这道题看起来字数很多 似乎很吓人  但其实我们仔细看一眼就发现 这不就是个求前缀和嘛  但是题目说了数据范围是10^9 但数据个数是10^5 因此我们自然而然想到用离散化节省空间

再分析 ,题目说了 n次 操作 m 次查询 一次查询肯定是两个下标 ,因此我们数组地大小肯定要达到3*(10^5)

其次,查询和操作一次都是两个元素的,因此我们额外定义一个类Pair 里边含有两个元素 first 和 second

在离散化的时候是需要查找下标的 我们通过二分来查找 因此首先要对数组进行排序并且去重     在存操作和询问时 我们用ArrayList来存储

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[] a=new int[300010];//离散化后的数组int[] s=new int[300010];//前缀和数组List<Integer> alls=new ArrayList();//存储下标List<Pair> add=new ArrayList();List<Pair> query=new ArrayList();while(n-->0){       //操作int x=sc.nextInt();int c=sc.nextInt();add.add(new Pair(x,c));alls.add(x);}for(int i=0;i<m;i++){   //先把查询下标录入alls和queryint l=sc.nextInt();int r=sc.nextInt();query.add(new Pair(l,r));alls.add(l);alls.add(r);}Collections.sort(alls);int unique=unique(alls);alls=alls.subList(0,unique);for(Pair item:add){     //离散加值处理int index=find(alls,item.first);a[index]+=item.second;}for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];  //求前缀和for(Pair item:query){           //查询int l=find(alls,item.first);int r=find(alls,item.second);System.out.println(s[r]-s[l-1]);}}public static int find(List<Integer> alls,int x){  //查找离散后下标 int l=0;int r=alls.size()-1;while(l<r){int mid=l+r>>1;if(alls.get(mid)>=x){r=mid;}else{l=mid+1;}}return l+1;   // 多加1是为了方便前缀和的求解}public static int unique(List<Integer> alls){int n=alls.size();int j=0;for(int i=0;i<n;i++){if(i==0||alls.get(i)!=alls.get(i-1)){alls.set(j++,alls.get(i));}}return j;}
}class Pair{int first;int second;public Pair(int first,int second){this.first=first;this.second=second;}}

tips:离散化其实利用了数之间的相对大小 把他们的大小顺序映射为a数组的下标 ,进而节省了空间,当我们需要查找一个数的下标时,我们也只需要二分查找即可  但是这道题求的是前缀和 其中不乏一些小细节 比如l+1 是为了后边方便求前缀和等等 确实是一道很好的离散化题目

8、区间合并

区间合并本质上来说像是贪心? 但是本质上并不难理解 代码也很简单 ,先对数组进行排序  维持start和end 区间  如果当前区间的start<end  那么就涉及合并 否则把前一个start ~end  加入到结果集中 最后循环结束 把剩余的一对start和end 加入到结果集中即可

例题一:区间合并

给定 nn 个区间 [li,ri][li,ri],要求合并所有有交集的区间。

注意如果在端点处相交,也算有交集。

输出合并完成后的区间个数。

例如:[1,3][1,3] 和 [2,6][2,6] 可以合并为一个区间 [1,6][1,6]。

输入格式

第一行包含整数 nn。

接下来 nn 行,每行包含两个整数 ll 和 rr。

输出格式

共一行,包含一个整数,表示合并区间完成后的区间个数。

数据范围

1≤n≤1000001≤n≤100000,
−109≤li≤ri≤109−109≤li≤ri≤109

输入样例:

5
1 2
2 4
5 6
7 8
7 9

输出样例:

3
import java.util.*;public class Main{public static void main(String[] args){Scanner sc=new Scanner(System.in);List<Pair> query=new ArrayList();int n=sc.nextInt();for(int i=0;i<n;i++){int l=sc.nextInt();int r=sc.nextInt();query.add(new Pair(l,r));}Collections.sort(query);int st=(int)-1e9;int ed=(int)-1e9;List<Pair> res=new ArrayList();//存放无法合并的数组for(Pair item:query){if(ed<item.first){if(ed!=-1e9) res.add(new Pair(st,ed));st=item.first;ed=item.second;}else{ed=Math.max(ed,item.second);}}if(ed!=(int)-1e9) res.add(new Pair(st,ed));  //记得最后我们退出循环时 是没有对上一步做总结的System.out.print(res.size());}
}
class Pair implements Comparable<Pair>{int first;int second;public Pair(int first,int second){this.first=first;this.second=second;}public int compareTo(Pair o){return Integer.compare(first,o.first);}
}

tips:此处必须要用一个list来存放无法合并的数组 而不能用res去记录合并的次数 因为最后我们要返回的是 无法合并数组的个数 而不是你合并了多少次!

还有就是排序时 默认Pair类是无法比较的 所以我们重写compare方法 !

以上就是我目前总结的算法笔记第一章啦 所有内容均来自yxc老师的算法基础课 ~  由于笔者水平有限 总结地方不乏一些错误 恳请大家斧正  也欢迎大家来评论区交流算法~~

感谢阅读!

算法笔记(一)(教你系统学习基础算法)相关推荐

  1. 布谷鸟哈希函数的参数_系统学习hash算法(哈希算法)

    系统学习hash算法(哈希算法) 转载请说明出处. 前言: 关于本文<系统学习hash算法>的由来.在看到了<十一.从头到尾彻底解析Hash 表算法>这篇文章之后,原文中没有暴 ...

  2. 机器学习,深度学习基础算法原理详解(图的搜索、交叉验证、PAC框架、VC-维(持续更新))

    机器学习,深度学习基础算法原理详解(图的搜索.交叉验证.PAC框架.VC-维.支持向量机.核方法(持续更新)) 机器学习,深度学习基础算法原理详解(数据结构部分(持续更新)) 文章目录 1. 图的搜索 ...

  3. 强化学习经典算法笔记(十二):近端策略优化算法(PPO)实现,基于A2C(下)

    强化学习经典算法笔记(十二):近端策略优化算法(PPO)实现,基于A2C 本篇实现一个基于A2C框架的PPO算法,应用于连续动作空间任务. import torch import torch.nn a ...

  4. 回溯算法:从电影蝴蝶效应中学习回溯算法的核心思想

    回溯算法:从电影<蝴蝶效应>中学习回溯算法的核心思想 数独.八皇后.0-1背包.图的着色.旅行商问题.全排列问题都能用到 理解"回溯算法" 回溯的思想,类似枚举搜索,枚 ...

  5. 算法笔记(胡凡)学习笔记@Kaysen

    本文旨在记录算法笔记学习过程中的收获和一些知识点,部分易错知识点只针对个人而言,CCF-CSP考试冲鸭!!! Chapter 2 C/C++快速入门(易错知识点) 2.1 基本数据类型 变量定义注意区 ...

  6. 【算法笔记】二分图最大权匹配 - KM算法(dfs版O(n4) + bfs版O(n3))

    整理的算法模板合集: ACM模板 匈牙利算法又称为 KM 算法,可以在 O(n3)O(n^3)O(n3) 时间内求出二分图的 最大权完美匹配 . 考虑到二分图中两个集合中的点并不总是相同,为了能应用 ...

  7. 手把手教你深度学习强大算法进行序列学习(附Python代码)

    作者:NSS 翻译:陈之炎 校对:丁楠雅 本文共3200字,建议阅读10分钟. 本文将教你使用做紧致预测树的算法来进行序列学习. 概述 序列学习是近年来深度学习的热点之一.从推荐系统到语音识别再到自然 ...

  8. 深度学习基础算法梳理

    1.实质用途 深度学习用来处理图像.语音等任务,也可用来处理数值型分类.回归任务.深度学习无需特征选择过程,具有较强的自学习能力,能拟合任意函数. 2.算法列表 2.1 感知器 感知器是由神经元,组成 ...

  9. 算法笔记-链相关、链的基础、单链双链环链、链的各种功能实现、链的算法题、面试题以及算法优化方法(多)、C#

    1. 链定义及其基础 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素.这组存储单元既可以是连续的,也可以是不连续的. 链表定义: 链表是一种线性表数据结构: 从底层存储 ...

  10. 算法笔记3.3C:等腰梯形(基础题)

    题目描述 请输入高度h,输入一个高为h,上底边长为h 的等腰梯形(例如h=4,图形如下). **************************** 输入 输入第一行表示样例数m,接下来m行每行一个整 ...

最新文章

  1. Hive metastore三种配置方式
  2. 100多次竞赛后,他研发了一个几乎可以解决所有机器学习问题的框架
  3. AWK用法详解(转载)
  4. ug后处理如何加密_什么叫UG编程?UG编程是干嘛的?不得不看哦!的UG
  5. Qt坐标系以及自定义可移动控件
  6. win8计算机可用内存不足,Win8.1玩游戏提示计算机内存不足,Win8.1内存不足怎么办?...
  7. MOS管及MOS管的驱动电路设计
  8. unsatisfied condition: __STDC_VERSION__ = 201112L
  9. 高性能Mysql(第三版)
  10. 透视特洛伊木马程序开发技术(转)
  11. sql server windows nt 64bit 内存占用过高
  12. python案例小游戏
  13. Docker部署Nebula Graph2.0和Studio
  14. linux得到网卡mac地址,获取 linux 网卡MAC地址(包含编程方法)
  15. 【日常分享】多邻国v4.93.4,在线学习英语、日语、韩语、德语…等30多种语言
  16. 16进制转double dotnet_IEEE 16进制字符串转化为double类型
  17. 随处可见的红黑树详解
  18. 计算机运算器实验原理,运算器实验原理.ppt
  19. html表单选择城市,基于weui的城市选择器(city-picker)
  20. AttentionTransformer

热门文章

  1. dreamweaver序列号免费_dreamweaver8【dreamweaver8序列号】dreamweaver8注册码序列号简体中文版...
  2. 染成茜色的坂道 文本提取(导出)方法
  3. 万年历java循环,万年历代码 java万年历源代码是多少?
  4. 程序员「在知乎装逼被怼」,决定用『面试』证明自己
  5. Sailfish OS构建(1)
  6. 哈工大中文分词系统ltp4j使用总结
  7. html面试要带电脑吗,前端面试巧妙回答浏览器兼容问题
  8. 【数学建模】历年全国大学生数学建模竞赛题目+定位分析
  9. ajaxSubmit异步提交
  10. SVPWM算法理解(一)——基本原理