第一章 基础算法

一、排序

1.快速排序

AcWing 785. 快速排序
AcWing 786. 第k个数
基本思想:取任意中间值x,通过每次循环将数组分为大于等于x和小于等于x的两部分,再递归遍历两边,直到每一部分都只有一个元素,序列即有序。
优点:不需要额外空间,算法稳定
缺点:时间复杂度 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n),但最坏情况下会变为 O ( n 2 ) O(n^2) O(n2)

void quick_sort(int q[],int l,int r){
	if(l>=r) return;//只有一个元素时已经有序 
	int x=q[l],i=l-1,j=r+1;//x是任意找的中间值,以q[l]为例 
						   //i和j是从两端开始的指针
	while(i<j){
		do i++; while(q[i]<x);//从最左边开始寻找第一个大于x的数 i指针指向这个数 
		do j--; while(q[j]>x);//从最右边开始寻找第一个小于x的数 j指针指向这个数 
		if(i<j) swap(q[i],q[j]);//如果之战没有相遇则交换 
	}
	quick_sort(q,l,j);//左半边递归 
	quick_sort(q,j+1,r);//右半边递归 
}

2.归并排序

AcWing 787. 归并排序
AcWing 788. 逆序对的数量
基本思想:将数组二分为单个元素,通过两个指针遍历使其两两合并,最终得到有序序列。
优点:算法稳定,时间复杂度稳定在 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)
缺点:需要 O ( n ) O(n) O(n)额外空间

void merge_sort(int q[],int l,int r){
	if(l>=r) return;//一个已经有序 
	int mid=(l+r)/2;//二分 
	merge_sort(q,l,mid);//递归左半边 
	merge_sort(q,mid+1,r);//递归右半边 
	int k=0,i=l,j=mid+1;//k为插入tmp数组的计数变量
						//i和j为二分的两个数组的指针 
	while(i<=mid&&j<=r){
		if(q[i]<=q[j]) tmp[k++]=q[i++];
		else tmp[k++]=q[j++];//小的插入中转数组tmp 
	}
	while(i<=mid) tmp[k++]=q[i++];
	while(j<=r) tmp[k++]=q[j++];//剩下的放在最后 
	for(int i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];//从tmp数组回到q 
}

二、二分

1.整数二分

AcWing 789. 数的范围

//区间[l,r]被分为[l,mid]和[mid+1,r]时 
int bsearch_1(int l,int r){
	while(l<r){
		int mid=l+r>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	return l;//返回l和r都可以 
}

//区间[l,r]被分为[l,mid-1]和[mid,r]时 
int bsearch_2(intl,int r){
	while(l<r){
		int mid=l+r+1>>1;//如果不+1 当l=r-1时会死循环
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	return l;//返回l和r都可以 
}

2.浮点数二分

AcWing 790. 数的三次方根

double bsearch_3(double l, double r){
    const double eps = 1e-8;//eps表示精度,取决于题目对精度的要求,一般比精度多两位,保证结果能够正确
    while (r-l>eps){//另一种方法:直接for循环100次,结果为n/(2^100),精度足够,但不建议
        double mid=(l+r)/2;
        if (check(mid)) r=mid;
        else l=mid;
    }
    return l;//l和r都可以 
}

三、高精度

1.高精度加法

AcWing 791. 高精度加法

//C=A+B
vector<int> add(vector<int> &A,vector<int> &B){
	vector<int> C;
	int t=0;//进位初始值为0 
	for(int i=0;i<A.size()||i<B.size();i++){
		if(i<A.size()) t+=A[i];
		if(i<B.size()) t+=B[i];
		C.push_back(t%10);
		t/=10;//有进位 => t=1
			  //无进位 => t=0 
	}
	if(t) C.push_back(1);//判断最高位进位 
	return C;
}

2.高精度减法

AcWing 792. 高精度减法

//C=A-B (A>=B) 
vector<int> sub(vector<int> &A,vector<int> &B){
	vector<int> C;
	int t=0;//借位初始值为0 
	for(int i=0;i<A.size();i++){
		t=A[i]-t;
		if(i<B.size()) t-=B[i];//总公式为 t=A[i]-B[i]-t
		C.push_back((t+10)%10);//t>0 => t
							   //t<0 => t+10 
		if(t<0) t=1;
		else t=0;//判断是否借位 
	}
	while(C.size()>1&&C.back()==0) C.pop_back();//去除前导0 
	return C;
}

3.高精度乘低精度

AcWing 793. 高精度乘法

//C=A*b
vector<int> mul(vector<int> &A,int b){
	vector<int> C;
	int t=0;//进位初始值为0 
	for(int i=0;i<A.size()||t;i++){//t为0时循环结束 
		if(i<A.size()) t+=A[i]*b;//循环A的每一位 与b整体相乘 
		C.push_back(t%10);
		t/=10;//下一位的进位 
	}
	while(C.size()>1&&C.back()==0) C.pop_back();//去除前导0 
	return C;
}

4.高精度除以低精度

AcWing 794. 高精度除法

//A/b=C...r
vector<int> div(vector<int> &A,int b,int &r){//余数r通过地址传出 
	vector<int> C;
	r=0;//余数初始值为0 
	for(int i=A.size()-1;i>=0;i--){
		r=r*10+A[i];//每一位余数=前一位余数*10+当前位 
		C.push_back(r/b);
		r%=b; 
	}
	reverse(C.begin(),C.end());//调换顺序 
	while(C.size()>1&&C.back()==0) C.pop_back();//去除前导0 
	return C;
}

四、前缀和

1.一维前缀和

AcWing 795. 前缀和
定义a[i]存储数据(a[0]=0),s[i]表示从a[1]到a[i]所有元素之和
即:
s i = ∑ n = 1 i a n s_i=\sum_{n=1}^ia_n si=n=1ian
题目要求获得区间[l,r]所有元素之和
则有:
∑ i = l r a i = s r − s l − 1 \sum_{i=l}^ra_i=s_r-s_{l-1} i=lrai=srsl1
优点:当有m个询问时,时间复杂度从O(mn)降为O(m+n)

2.二维前缀和

AcWing 796. 子矩阵的和
定义a[i][j]存储数据,s[i][j]表示s[i][j]及其左上方所有元素之和
即:
s i , j = ∑ n = 1 i ∑ m = 1 j a n , m s_{i,j}=\sum_{n=1}^i\sum_{m=1}^ja_{n,m} si,j=n=1im=1jan,m
获取s[i][j]方法:
s i , j = s i − 1 , j + s i , j − 1 − s i − 1 , j − 1 + a i , j s_{i,j}=s_{i-1,j}+s_{i,j-1}-s_{i-1,j-1}+a_{i,j} si,j=si1,j+si,j1si1,j1+ai,j
题目要求获得从( x 1 , y 1 x_1,y_1 x1,y1)到( x 2 , y 2 x_2,y_2 x2,y2)间所有元素之和
则有:
∑ n = x 1 x 2 ∑ m = y 1 y 2 a n , m = s x 2 , y 2 − s x 1 − 1 , y 2 − s x 2 , y 1 − 1 + s x 1 − 1 , y 1 − 1 \sum_{n=x_1}^{x_2}\sum_{m=y_1}^{y_2}a_{n,m}=s_{x_2,y_2}-s_{x_1-1,y_2}-s_{x_2,y_1-1}+s_{x_1-1,y_1-1} n=x1x2m=y1y2an,m=sx2,y2sx11,y2sx2,y11+sx11,y11
优点:当有m个询问时,时间复杂度从 O ( m n 2 ) O(mn^2) O(mn2)降为 O ( m + n 2 ) O(m+n^2) O(m+n2)

五、差分

(其实就是前缀和的逆运算)
优点同样也是降低时间复杂度

1.一维差分

AcWing 797. 差分
类比一维前缀和,定义s[i]存储数据,且令a[i]+s[i-1]=s[i]
则有:
s i = ∑ n = 1 i a n s_i=\sum_{n=1}^ia_n si=n=1ian
题目要求给区间[l, r]中的每个数加上c:

s[l]+=c;  //l后面都加c
s[r+1]-=c;//r+1后面都减c

2.二维差分

AcWing 798. 差分矩阵
类比二维前缀和,定义s[i][j],a[i][j]
则有:
s i , j = ∑ n = 1 i ∑ m = 1 j a n , m s_{i,j}=\sum_{n=1}^i\sum_{m=1}^ja_{n,m} si,j=n=1im=1jan,m
获取a[i][j]方法:
a i , j = s i , j − s i − 1 , j − s i , j − 1 + s i − 1 , j − 1 a_{i,j}=s_{i,j}-s_{i-1,j}-s_{i,j-1}+s_{i-1,j-1} ai,j=si,jsi1,jsi,j1+si1,j1
题目要求给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:

s[x1, y1] += c;
s[x2 + 1, y1] -= c;
s[x1, y2 + 1] -= c;
S[x2 + 1, y2 + 1] += c;

六、双指针算法

AcWIng 799. 最长连续不重复子序列
AcWing 800. 数组元素的目标和

for (int i = 0, j = 0; i < n; i ++ ){
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}

常见问题分类:
(1) 对于一个序列,用两个指针维护一段区间
(2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

七、位运算

AcWing 801. 二进制中1的个数

//求n的第k位数字
n >> k & 1
//返回n的最后一位1
lowbit(n) //= n & -n

八、离散化

AcWing 802. 区间和
当题目给定一个稀疏序列时,要将每个所需元素对应的下标映射到从1到n,从而降低时空复杂度

vector<int> alls; //存储所有待离散化的值
sort(alls.begin(), alls.end()); //将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); //去掉重复元素

//二分求出x对应的离散化的值
int find(int x){ //找到第一个大于等于x的位置
    int l = 0, r = alls.size() - 1;
    while (l < r){
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; //映射到从1到n
}

九、区间合并

AcWing 803. 区间合并

// 将所有存在交集的区间合并
void merge(vector<PII> &segs){
    vector<PII> res;
    sort(segs.begin(), segs.end());
    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first){
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);
    if (st != -2e9) res.push_back({st, ed});
	segs = res;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值