快排
时间复杂度为O(nlogn),注意边界问题,最好将下边的算法过程背过。
快排的话一般上机考试很少用到,但是面试的时候面试官会让手写快排
#include<iostream>
using namespace std;
const int N = 100010;
int q[N];
int n;
void quick_sort(int q[],int l,int r){
if(l >= r) return;
int x = q[(l+r)/2], i = l-1, j = r+1;
//标记值x取中间,不要取第一个或者最后一个,如果数组里全是相同的数字,那么复杂度就变成了n²
while(i < j){
do i++; while (q[i]<x);
do j--; while (q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q, l, j);
quick_sort(q, j+1, r);
}
int main(){
scanf("%d",&n);
int i;
for(i=0;i<n;i++)
scanf("%d",&q[i]);
quick_sort(q, 0, n-1);
for(i=0;i<n;i++)
printf("%d ",q[i]);
return 0;
}
归并排序
时间复杂度为O(nlogn),是稳定排序
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N],temp[N];
void merge_sort(int a[],int l,int r){
int mid;
if(l >= r) return;
mid = (l+r)/2;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int i = l, j = mid+1;
int k = 0;
while(i<=mid && j<=r){
if(a[i]<=a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while(i<=mid) temp[k++] = a[i++];
while(j<=r) temp[k++] = a[j++];
for(i=l,j=0;i<=r;i++,j++)
a[i] = temp[j];
}
int main(){
cin>>n;
int i;
for(i=0;i<n;i++)
scanf("%d",&a[i]);
merge_sort(a,0,n-1);
for(i=0;i<n;i++)
printf("%d ",a[i]);
return 0;
}
直接使用排序函数
//包含头文件
#include<algorithm>
//使用方法
//对a[10]进行排序
sort(a,a+10);
二分法
注意二分的什么时候需要+1,很容易陷入死循环
int l = 0, r = n-1;
int mid;
//r = mid 不需要加1
while(l<r){
mid = (l+r)/2;
if(k<=a[mid]) r = mid;
else l = mid + 1;
}
//l = mid需要加1
l=0;
r=n-1;
while(l<r){
mid = (l+r+1)/2;
if(k>=a[mid]) l = mid;
else r = mid - 1;
}
高精度运算
对于高精度的加减乘除法,由于位数太多数字变量无法储存,所以需要将其储存在数组中。由于运算需要有进位和借位,所以对于加、减、乘需要逆序存储,而对于除法直接顺序存储就可以。
前缀和(常用)
一维前缀和
计算数组a[l,r]区间内的和
注意下标需要从1开始,便于处理边界。
#include<iostream>
using namespace std;
const int N = 1e5 +10;
int a[N];
int s[N];
int n,m;
int main(){
cin>>n>>m;
int i;
int l,r;
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++) //计算所有前缀和
s[i] = s[i-1] + a[i];
for(i=0;i<m;i++){
scanf("%d%d",&l,&r);
printf("%d\n",s[r]-s[l-1]); //输出[l,r]区间的前缀和
}
return 0;
}
二维前缀和(子矩阵的和)
注意记住公式,而且下标要从0开始
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N];
int s[N][N];
int n,m,q;
int main(){
cin>>n>>m>>q;
int i,j;
int x1,y1,x2,y2;
for(i=1;i<=n;i++){
for(j=1;j<=m;j++)
scanf("%d",&a[i][j]);
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++)
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]; //计算所有前缀和
}
while(q--){
cin>>x1>>y1>>x2>>y2;
printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]); //计算(x1,y1)和(x2,y2)之间的和
}
return 0;
}
差分
差分是前缀和的逆运算。
一维差分
对于区间[l,r]上的每个数加上c,时间复杂度为O(1)。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int b[N];
int n,m;
void insert(int l,int r,int c){
b[l] += c;
b[r+1] -= c;
}
int main(){
cin>>n>>m;
int i;
int l,r,c;
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
for(i=1;i<=n;i++)
insert(i,i,a[i]); //假设为0,执行n次插入进行赋值
while(m--){
cin>>l>>r>>c;
insert(l,r,c);
}
for(i=1;i<=n;i++) //计算自身的前缀和并储存在自身
b[i] += b[i-1];
for(i=1;i<=n;i++)
printf("%d ",b[i]);
return 0;
}
二维差分
对(x1,y1)和(x2,y2)区间里的数加上c,本来需要O(n)进行枚举,现在只需要改变4个数。
#include<iostream>
using namespace std;
const int N = 1010;
int a[N][N];
int b[N][N];
int n,m,q;
void insert(int x1, int y1, int x2, int y2, int c){
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}
int main(){
cin>>n>>m>>q;
int i,j;
int x1,y1,x2,y2,c;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(i=1;i<=n;i++) //对二维数组进行初始化
for(j=1;j<=m;j++)
insert(i,j,i,j,a[i][j]);
while(q--){
cin>>x1>>y1>>x2>>y2>>c;
insert(x1,y1,x2,y2,c);
}
for(i=1;i<=n;i++){ //计算自身的前缀和并储存
for(j=1;j<=m;j++){
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
}
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
printf("%d ",b[i][j]);
}
printf("\n");
}
return 0;
}
双指针算法
核心思想是将O(n²)的暴力枚举变为O(n)。比较常见,主要是一种解题思考的思路,快排和归并排序等都有用到双指针算法。
解题思路:先用暴力方法写出来,然后看 i 和 j 之间有没有单调关系,如果有,就可以从暴力变为双指针。
for(int i=0,int j=0;i<n;i++){
while(j<i && check(i,j))
j++;
//算法对应的题目要求
}
位运算
- n的二进制表示中第k位是几
① 先把第k位移到最后,n>>k
② 看个位是几,n&1 - lowbi(x) 返回x的最右边的最后一位1,
表达式: x & -x
离散化
掌握离散化的思想,如果数据值达到1e9,那么对应的数组不能开到1e9这么大,但是如果输入是稀疏离散的,可以按照输入顺序进行存储数据值,那么数组只用开到1e5的大小。