算法基础课

文章目录

第一讲——基础算法(快速排序、归并排序、二分、高精度、前缀和与差分、双指针算法、位运算、离散化、区间合并等内容。)

一、快速排序

1、快速排序

给定你一个长度为 n 的整数数列。

请你使用快速排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

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

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

1.1 思想:
  • 基本思想:分治(分而治之)
  • 先从数列中取出一个数作为基准数x,再分区
  • 分区过程,将比基准数x大的数全放到基准数x的右边,小于等于基准数的数全放到它的左边
  • 再对左右区间重复上述操作,直到各个区间只有一个数
1.2 步骤:
  • 确定分界点: 左边界 q[ l ],基准数 q[ (l+r)/2 ],右边界 q[ r ]

在这里插入图片描述

  • 递归处理左右边界
1.3 实现方法一:
  • 建立两个数组a[ ],b[ ]
  • 遍历q[ l~r ],如果q[ i ]<=x,将x插入a[ ];如果q[ i ]>x,将x插入b[ ]
  • 先后将a[ ],b[ ]放入q[ ]中
    在这里插入图片描述
1.4 实现方法二(优雅):
  • 设置两个指针i和j
  • i从 q[ l ] 开始遍历,如果 i <=x,就继续向右走,直到 i >x,停下
  • j从 q[ r ] 开始遍历,如果 j <=x,就继续向左走,直到 j <x,停下
  • 然后swap交换 i 和 j ,i 和 j 再分别移动一位,再判断是否 i > x ,j < x
  • (注意由于数据较多,C++中不用cin ,而是用scanf 较快,java中不用scanner ,而用Buffered Reader)
1.5 代码(方法二):

2、第k个数

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

输入格式
第一行包含两个整数 n 和 k。

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

输出格式
输出一个整数,表示数列的第 k 小数。

数据范围
1≤n≤100000,
1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3

二、归并排序

1、归并排序

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式
输入共两行,第一行包含整数 n。

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

输出格式
输出共一行,包含 n 个整数,表示排好序的数列。

数据范围
1≤n≤100000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5

2、逆序对的数量

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

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

输入格式
第一行包含整数 n,表示数列的长度。

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

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

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

输入样例:
6
2 3 4 5 6 1
输出样例:
5

三、二分

1、数的范围

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

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

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

输入格式
第一行包含整数 n 和 q,表示数组长度和询问个数。

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

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

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

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

数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1

2、数的三次方根

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

输入格式
共一行,包含一个浮点数 n。

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

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

数据范围
−10000≤n≤10000
输入样例:
1000.00
输出样例:
10.000000

四、高精度

1、高精度加法

给定两个正整数(不含前导 0),计算它们的和。

输入格式
共两行,每行包含一个整数。

输出格式
共一行,包含所求的和。

数据范围
1≤整数长度≤100000
输入样例:
12
23
输出样例:
35

2、高精度减法

给定两个正整数(不含前导 0),计算它们的差,计算结果可能为负数。

输入格式
共两行,每行包含一个整数。

输出格式
共一行,包含所求的差。

数据范围
1≤整数长度≤105
输入样例:
32
11
输出样例:
21

3、高精度乘法

给定两个非负整数(不含前导 0) A 和 B,请你计算 A×B 的值。

输入格式
共两行,第一行包含整数 A,第二行包含整数 B。

输出格式
共一行,包含 A×B 的值。

数据范围
1≤A的长度≤100000,
0≤B≤10000
输入样例:
2
3
输出样例:
6

4、高精度除法

给定两个非负整数(不含前导 0) A,B,请你计算 A/B 的商和余数。

输入格式
共两行,第一行包含整数 A,第二行包含整数 B。

输出格式
共两行,第一行输出所求的商,第二行输出所求余数。

数据范围
1≤A的长度≤100000,
1≤B≤10000,
B 一定不为 0
输入样例:
7
2
输出样例:
3
1

五、前缀和与差分

1、前缀和

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

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

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

输入格式
第一行包含两个整数 n 和 m。

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

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

输出格式
共 m 行,每行输出一个询问的结果。

数据范围
1≤l≤r≤n,
1≤n,m≤100000,
−1000≤数列中元素的值≤1000
输入样例:
5 3
2 1 3 6 4
1 2
1 3
2 4
输出样例:
3
6
10

2、子矩阵的和

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

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

输入格式
第一行包含三个整数 n,m,q。

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

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

输出格式
共 q 行,每行输出一个询问的结果。

数据范围
1≤n,m≤1000,
1≤q≤200000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−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

3、差分

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

接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

请你输出进行完所有操作后的序列。

输入格式
第一行包含两个整数 n 和 m。

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

接下来 m 行,每行包含三个整数 l,r,c,表示一个操作。

输出格式
共一行,包含 n 个整数,表示最终序列。

数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
输入样例:
6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
输出样例:
3 4 5 3 4 2

4、差分矩阵

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

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

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

输入格式
第一行包含整数 n,m,q。

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

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

输出格式
共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围
1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤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

六、双指针

1、最长连续不重复子序列

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

输入格式
第一行包含整数 n。

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

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

数据范围
1≤n≤105
输入样例:
5
1 2 2 3 5
输出样例:
3

数组元素的目标和

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。

数组下标从 0 开始。

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

数据保证有唯一解。

输入格式
第一行包含三个整数 n,m,x,分别表示 A 的长度,B 的长度以及目标值 x。

第二行包含 n 个整数,表示数组 A。

第三行包含 m 个整数,表示数组 B。

输出格式
共一行,包含两个整数 i 和 j。

数据范围
数组长度不超过 105。
同一数组内元素各不相同。
1≤数组元素≤109
输入样例:
4 5 6
1 2 4 7
3 4 6 8 9
输出样例:
1 1

3、判断子序列

给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm。

请你判断 a 序列是否为 b 序列的子序列。

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

输入格式
第一行包含两个整数 n,m。

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

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

输出格式
如果 a 序列是 b 序列的子序列,输出一行 Yes。

否则,输出 No。

数据范围
1≤n≤m≤105,
−109≤ai,bi≤109
输入样例:
3 5
1 3 5
1 2 3 4 5
输出样例:
Yes

七、位运算

1、二进制中1的个数

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

输入格式
第一行包含整数 n。

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

输出格式
共一行,包含 n 个整数,其中的第 i 个数表示数列中的第 i 个数的二进制表示中 1 的个数。

数据范围
1≤n≤100000,
0≤数列中元素的值≤109
输入样例:
5
1 2 3 4 5
输出样例:
1 1 2 1 2

八、离散化

1、区间和

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

现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。

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

输入格式
第一行包含两个整数 n 和 m。

接下来 n 行,每行包含两个整数 x 和 c。

再接下来 m 行,每行包含两个整数 l 和 r。

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

数据范围
−109≤x≤109,
1≤n,m≤105,
−109≤l≤r≤109,
−10000≤c≤10000
输入样例:
3 3
1 2
3 6
7 5
1 3
4 6
7 8
输出样例:
8
0
5

九、区间合并

1、区间合并

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

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

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

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

输入格式
第一行包含整数 n。

接下来 n 行,每行包含两个整数 l 和 r。

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

数据范围
1≤n≤100000,
−109≤li≤ri≤109
输入样例:
5
1 2
2 4
5 6
7 8
7 9
输出样例:
3

第二讲——数据结构(单链表,双链表,栈,队列,单调栈,单调队列,KMP,Trie,并查集,堆,哈希表)

一、单链表

1、单链表

在这里插入图片描述

输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5

实现方式1:结构体+链表(很慢,因此暂时不用)
struct Node
{
	int val;
	Node *next;
}
new Node();//非常慢
实现方式2:数组模拟链表:

静态数组模拟链表比动态指针链表速度要快

单链表中用的最多的是邻接表(邻接表,实际就是n个链表)
邻接表的用途:存储
双链表的作用:优化某些问题

在这里插入图片描述

定义:
//head表示: 头结点的下标  
//e[i]表示: 结点i的值
//ne[i]表示:结点i的next指针是多少,即结点i的下一个值的下标 
//idx:      存储当前用到了哪个点,相当于指针 
int head,e[N],ne[N],idx;
初始化:
void init()
{
	head=-1; //表示空集 
	idx=0;   //点从0开始分配	 
}
插入:

时间复杂度:O(1)
在这里插入图片描述
插入操作1:将一个数 x 插入到头结点后

//第一步:将指针指向head的下一个位置
//第二步:将head原本指向(head下一位)的指针删掉,改为(head指向 x)

//idx存的是:当前可以用的最新点的下标
void add_to_head(int x)
{
	e[idx]=x;     //将x的值存起来
	 
	ne[idx]=head; //ne[idx]代替了原本head的位置 
	head=idx;     //将head放到idx前
	idx++;
} 

插入操作2:将一个数x插入到(下标为k的点)后

//和上面在头结点后面插入数用法类似,将head换成ne[k] 
void add(int k,int x)
{
	e[idx]=x;
	ne[idx]=ne[k];
	ne[k]=idx;
	idx++:
} 

删除操作:

将(下标为k的点)的后面一个点 删掉 在这里插入图片描述

void remove(int k)
{
	//跳过一个数,则指针ne也要跳过一个 
	//ne[ne[k]]表示:指向(当前指向ne[k])的下一个指向 
	ne[k]=ne[ne[k]];
}
AC代码:
#include<iostream>
using namespace std;
const int N=100010;

//定义
int head,e[N],ne[N],idx;

//初始化
void init()
{
	head=-1;
	idx=0;
}

//操作1:将x插入到头结点
void add_to_head(int x)
{
	e[idx]=x,ne[idx]=head,head=idx,idx++;
}

//操作2:删除第k个插入的数后面的数
//因为idx初始化为0,每次插入一个新的数后,idx+1
//所以 第一个插入的点的下标为0,第k个插入的点的下标为k-1
//因此:第k个删掉点,就是下标为 k-1的点
void remove(int k)
{
	ne[k]=ne[ne[k]];
}

//操作3:在第k个插入的数后面插入一个数
//即:将x插入到下标是k的点后面
void add(int k,int x)
{
	e[idx]=x,ne[idx]=ne[k],ne[k]=idx,idx++;
}
int main()
{
	int m;
	cin>>m;
	init();
	
	while(m--)
	{
		int k,x;
		char s;
		cin>>s;
		if(s=='H')//操作1 
		{
			cin>>x;
			add_to_head(x); 
		}
		if(s=='D')//操作2 
		{
			cin>>k;
			if(k==0)//当k为0时,删除掉头结点 
				head=ne[head]; 
			else
				remove(k-1);
		}
		if(s=='I')//操作3 
		{
			cin>>k>>x;
			add(k-1,x);//注意不要写成k
		}
	}
	
	//遍历输出
	for(int i=head; i!=-1; i=ne[i])
		cout<<e[i]<<' ';
		
	cout<<endl;
	return 0;
}

二、双链表

1、双链表

在这里插入图片描述

输入样例:
10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2
输出样例:
8 7 7 3 2 9

1.1 初始化
//初始化
void init()
{
	//1表示右端点.0表示左端点
	r[0]=1,l[1]=0;

	//0和1已经用过了,所以当前指针idx从2开始
	idx=2;
}
1.2 插入图解

在这里插入图片描述

线条表示:
圈1表示:旧的r[k]2表示:l[k+1]3表示:新的l[k+1]4表示:创建的r[idx]5表示:新的r[k]6表示:创建的l[idx] 

过程:
圈1改为圈6,但此时圈1还在,没有被覆盖掉
创建圈41+3,这个过程中将圈2覆盖掉,即圈2被删掉了
覆盖掉圈1相当于删除圈1,并添加圈5
1.3 插入代码表示
在第k点的右边插入一个数x
void add(int k,int x)
{
	e[idx]=x;//将值存起来

	//将k的右指针赋值给当前idx的右指针
	//(圈1改为圈6,但此时圈1还在,没有被覆盖掉) 
	r[idx]=r[k];

	//此时k成为了idx的左指针
	//(创建圈4) 
	l[idx]=k;

	//k的右指针指向idx
	
	//k的原右指针为r[k],因此在插入之前
	//l[r[k]]先指向(k+1),再从(k+1)向左指回来,指向k自己
	
	//插入的过程中,r[k]还是指向(k+1),
	//但是在向左指回来时,
	//是指向新插入的idx,
	//(圈1+圈3,这个过程中将圈2覆盖掉,即删掉了) 
	l[r[k]]=idx;
	
	//(覆盖掉圈1相当于删除圈1,并添加圈5)
	r[k]=idx;   
}

1.4 删除图解

在这里插入图片描述

1.5 删除代码表示:
//删除 
void remove(int )
{
	r[l[k]]=r[k];
	l[r[k]]=l[k];
}
1.6 AC代码
#include<iostream>
using namespace std;
const int N=100010;

int m;

//定义
//存值、左指针、右指针、当前指针
int e[N],l[N],r[N],idx;

//初始化
void init()
{
	//1表示右端点.0表示左端点
	r[0]=1,l[1]=0;

	//0和1已经用过了,所以当前指针idx从2开始
	idx=2;
}

//插入
//有两种选择,可以插入某点的左边,
//也可以插入某点的右边

//插入1:在第k点的右边插入一个数x
void add(int k,int x)
{
	e[idx]=x;
	r[idx]=r[k];
	l[idx]=k;
	l[r[k]]=idx;
	r[k]=idx;  
	idx++;
}

//删除 第k个点 
void remove(int k)
{
	r[l[k]]=r[k];
	l[r[k]]=l[k];
}

int main()
{
	//cin,cout先把要输出的东西存入缓冲区,
	//再输出,导致效率降低,
	//而这段语句可以来打消iostream的输入输出缓存,
	//可以节省许多时间,
	//使效率与scanf与printf相差无几
	ios::sync_with_stdio(false);
	
	cin>>m;
	
	init();
	
	while(m--)
	{
		string s;
		cin>>s;
		int k,x;
		
		if(s=="R")
		{
			cin>>x;
			add(l[1],x);
		}
		else if(s=="L")
		{
			cin>>x;
			add(0,x);
		}
		else if(s=="D")
		{
			cin>>k;
			remove(k+1);
		}
		else if(s=="IL")
		{
			cin>>k>>x;
			add(l[k+1],x);
		}
		else
		{
			cin>>k>>x;
			add(k+1,x);
		}
	}
	
	for(int i=r[0];i!=1;i=r[i])
		cout<<e[i]<<' ';
	return 0;
}

三、栈

1、模拟栈

在这里插入图片描述
输入样例:
10
push 5
query
push 6
pop
query
pop
empty
push 4
query
empty
输出样例:
5
5
YES
4
NO

1.1 栈的图解

栈(封底):先进后出
队列(不封底):先进先出

在这里插入图片描述

1.2 定义
//栈       栈顶的下标 
int stk[N],tt; 
1.3 插入
//插入一个数x 
stk[++tt]= x;
1.4 删除
//弹出(删除)
t--;
1.5 判断栈是否为空
if(tt>0)//不空 
	not empty; 
else
	empty;
1.6 取出栈顶元素
stk[tt];

1.7 AC代码
#include<iostream>
using namespace std;

const int N=100010;
int stk[N],tt;

void push(int x)//操作1
{
	tt++;
	stk[tt]=x;
}

void pop()//操作2
{
	tt--;
}

void empty()//操作3
{
	if(tt)//if(tt>0)
		cout<<"NO"<<endl;
	else 
		cout<<"YES"<<endl;
}

int query()//操作4
{
	return stk[tt];
}

int main()
{
	int n;
	cin>>n;
	while(n--)
	{
		string s;
		cin>>s;
		
		if(s=="push")
		{
			int x;
			cin>>x;
			push(x);//需要传入插入的数
		}
		else if(s=="pop")
			pop();
		else if(s=="empty")
			empty();
		else
			cout<<query()<<endl;
	}
	return 0;
}

2、表达式求值

在这里插入图片描述

2.1 动画演示B站链接

https://www.bilibili.com/video/BV1wy4y1h7d9?p=1&vd_source=bcc0d3ed1b2e6261a99ba43cfbe3575f

(悄悄吐槽两句:1、这真的是碳基生物能想出来的算法吗?2、动画演示短短三四分钟,却给了一个小时的心理阴影)

2.2 逆波兰式(后缀表达式)(将运算符写在操作数之后)

如:我们平时写a+b,这是中缀表达式,写成后缀表达式就是:ab+

1、检查字符串[ { ( ) } ],括号是否可以左右对上

注意:只有[ { ( ) } ]这一种顺序可以配对成功,左右字符不对称时匹配失败,如:[ { ( ] } ),左右不能对上

传统写法:逐个检查,逐个配对,但时间复杂度较大

如何只检查一遍 O(n)就可以完成配对?
栈的优化计算:
1.1、所有左括号都入栈
在这里插入图片描述
1.2、所有右括号都要让栈顶出栈,并和当前的右括号进行比较,发现配对成功
在这里插入图片描述

1.3、那么接下来的右括号,也要让栈顶出栈进行匹配

在这里插入图片描述
在这里插入图片描述

2、计算机如何高效读取并计算这样的算式:1+(6+2)*3)

传统计算方法:
遍历所有字符,检查是否有括号,优先计算,再遍历检查是否有乘号*或除号/,其次计算,再遍历检查是否有加号+或减号-,最后计算

这种中缀表达式计算效率较低

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

栈的优化计算 :将中缀表达式 转化成 后缀表达式 ,再利用栈进行计算

中缀表达式 转化成 后缀表达式规则:
在这里插入图片描述
中缀表达式:
在这里插入图片描述
转化:
1、第一个字符为数字(规则1),直接输出
在这里插入图片描述

2、第二个字符为加号(规则2),入栈
在这里插入图片描述
3、第3个字符为左括号(规则3),入栈
在这里插入图片描述
4、第4个字符为数字6(规则1),直接输出
在这里插入图片描述
5、第5个字符为加号(规则4),入栈
在这里插入图片描述
6、第6个字符为数字2,直接输出
在这里插入图片描述
7、第7个字符为右括号(规则5),加号+出栈,碰到左括号,匹配成功,消掉
在这里插入图片描述

8、第8个字符为乘号*(规则2),入栈
在这里插入图片描述
9、第9个字符为数字3,直接输出
在这里插入图片描述
10、最后将栈中元素依次出栈,就有得到了后缀表达式
在这里插入图片描述

有了后缀表达式之后的规则:
一、凡是数字就压栈
二、凡是运算符就出栈两个元素,让这两个元素进行上面运算符的计算,再将计算结果入栈

计算:
1、将1、6、2 压栈(规则1)
在这里插入图片描述
(2、规则2)
在这里插入图片描述
在这里插入图片描述
3、(规则1)
在这里插入图片描述

4、将栈顶两个元素进行相乘后,将相乘结果24压栈(规则2)
在这里插入图片描述
5、将24与1进行相加,变成25(规则2)
在这里插入图片描述

6、结束运算

2.3 代码:(吐血整理中)

四、队列

1、模拟队列

在这里插入图片描述
输入样例:
10
push 6
empty
query
pop
empty
push 3
push 4
pop
query
push 6
输出样例:
NO
6
YES
4

在这里插入图片描述

五、单调栈

1、单调栈

在这里插入图片描述

输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2

六、单调队列

1、滑动窗口

在这里插入图片描述
输入样例:
8 3
1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7

七、KMP

1、KMP字符串

在这里插入图片描述输入样例:
3
aba
5
ababa
输出样例:
0 2

八、Trie

1、Trie字符串统计

在这里插入图片描述
输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab
输出样例:
1
0
1

2、最大异或对

在这里插入图片描述
输入样例:
3
1 2 3
输出样例:
3

九、并查集

1、合并集合

在这里插入图片描述
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes

2、连通块中点的数量

在这里插入图片描述
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3

3、食物链

在这里插入图片描述
输入样例:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例:
3

十、堆

1、堆排序

在这里插入图片描述
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3

2、模拟堆

在这里插入图片描述
输入样例:
8
I -10
PM
I -10
D 1
C 2 8
I 6
PM
DM
输出样例:
-10
6

十一、哈希表

1、模拟散列表

在这里插入图片描述
输入样例:
5
I 1
I 2
I 3
Q 2
Q 5
输出样例:
Yes
No

2、字符串哈希

在这里插入图片描述
输入样例:
8 3
aabbaabb
1 3 5 7
1 3 6 8
1 2 1 2
输出样例:
Yes
No
Yes

第三讲——搜索与图论(DFS,BFS,树与图的深度优先遍历,树与图的广度优先遍历,拓扑排序,Dijkstra,bellman-ford,spfa,Floyd,Prim,Kruskal,染色法判定二分图,匈牙利算法)

一、DFS

1、排列数字

在这里插入图片描述

输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

1.1 AC代码
#include<iostream>
using namespace std;

const int N=10;
int path[N];//保存序列
int state[N];//数字是否被用过,初始化为0,表示没有用过 
int n;

void dfs(int u)
{ 
    /*
    如果主函数从dfs(0)开始,这里就写if(u==n)
	如果主函数从dfs(1)开始,这里就写if(u>n)
    */
	if(u==n)	//到了最深层,数字填完了输出 
	{
	    //这里不能写for(int i=1;i<=n;i++),因为dfs(0)下标是从0开始
		for(int i=0;i<n;i++)
		{
			cout<<path[i]<<" ";
		}
		cout<<endl;
	}	
	
	//数字还没填完,遍历 
	for(int i=1;i<=n;i++)
	{
		if(!state[i])//如果数字没有被用过
		{
			path[u]=i;//放入空位 //u为当前位置
			state[i]=1; 
			dfs(u+1);//填下一位
			//path[u]=0;
			state[i]=0;//回溯当上一层时,将这一层下面的数,重新修改状态为没有用过
		 } 
	}
} 

int main()
{
	cin>>n;
	dfs(0);
}
1.2 AC代码
#include<iostream>
using namespace std;

const int N=10;
int path[N];//保存序列
int state[N];//数字是否被用过,初始化为0,表示没有用过 
int n;

void dfs(int u)
{
    
    //不能写if(u==n),因为main函数不是dfs(0),而是dfs(1),从下标1开始,
    //u就必须>n才能输出
    
	if(u>n)	//到了最深层,输出 
	{
		for(int i=1;i<=n;i++)
		{
			cout<<path[i]<<" ";
		}
		cout<<endl;
	}	
	
	//u<=n,数字还没填完,遍历 填数字
	for(int i=1;i<=n;i++)
	{
		if(!state[i])//如果数字没有被用过
		{
			path[u]=i;//保存序列
			state[i]=1; //状态表示用过了
			
			dfs(u+1);//填下一位
			//path[u]=0;
			state[i]=0;//回溯
		 } 
	}
} 

int main()
{
	cin>>n;
	dfs(1);
}

2、n皇后问题

在这里插入图片描述
输入样例:
4
输出样例:
.Q…
…Q
Q…
…Q.

…Q.
Q…
…Q
.Q…

2.1 分析

在这里插入图片描述
在这里插入图片描述(上图来源:acwing白马金羁侠少年)

2.3 解法1

旧的解法,按行分析,时间复杂度较低,为O(n!)
按行来枚举,因此没有row[N]

#include<iostream>
using namespace std;
const int N=20;

//bool数组用来判断搜索的下一个位置是否可行
//col列,dg对角线,udg反对角线
//g[N][N]用来存路径

int n;
char g[N][N];

//表示列、正对角线、负对角线 的状态 
bool col[N],dg[N],udg[N];

void dfs(int u)
{
	
	//u==n表示已经搜了n行,故输出这条路径
	if(u==n)
	{
		for(int i=0;i<n;i++)
		{
			cout<<g[i]<<endl;
		}
		cout<<endl;//因为有可能有多种解法,因此,每种解法结束后都加个空行 
	} 
	
	//枚举u这一行,搜索合法的列
	
	int x=u;
	for(int y=0;y<n;y++)
	{
		//剪枝(对于不满足要求的点,不再继续往下搜索)
		if(col[y]==false&&dg[y-x+n]==false&&udg[y+x]==false)
		{
			col[y]=dg[y-x+n]=udg[y+x]=true;
			g[x][y]='Q';
			dfs(x+1);
			g[x][y]='.';//恢复现场
			col[y]=dg[y-x+n]=udg[y+x]=false; 
		} 
	} 
} 

int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
			g[i][j]='.';
	}
	dfs(0);
	return 0;
}
2.4 解法2

按元素枚举,因此要加上row[N]的行状态
时间复杂度较大,为O(2的n方次方)

#include <iostream>
using namespace std;
const int N = 20;

int n;
char g[N][N];
bool row[N], col[N], dg[N], udg[N]; // 因为是一个个搜索,所以加了row

// s表示已经放上去的皇后个数
void dfs(int x, int y, int s)//坐标(x,y),记录皇后数量为s 
{
	// 一行结束,处理即将超出边界的情况
	if (y == n) 
	{
		y = 0;//换行 
		x ++ ;
	}

	if (x == n)   // x==n说明已经枚举完n*n个位置了
	{
		if (s == n)   // s==n说明成功放上去了n个皇后
		{
			for (int i = 0; i < n; i ++ ) 
				cout<<g[i]<<endl;
			cout<<endl;//每种情况输出结束后,用空行隔开 
				
		}
		return;
	}

	// 分支1:放皇后,s+1 
	if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
	{
		g[x][y] = 'Q';//满足皇后条件,放皇后 
		row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;//标记状态为放过皇后了 
		
		dfs(x, y + 1, s + 1);
		
		//恢复现场 
		row[x] = col[y] = dg[x + y] = udg[x - y + n] = false; 
		g[x][y] = '.'; 
	}

	// 分支2:不放皇后,s没有+1 
	dfs(x, y + 1, s);
}


int main()
{
	cin >> n;
	//初始化现场 全为. 
	for (int i = 0; i < n; i ++ )
		for (int j = 0; j < n; j ++ )
			g[i][j] = '.';

	dfs(0, 0, 0);

	return 0;
}

二、BFS

1、走迷宫

在这里插入图片描述
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int N = 110;

typedef pair<int, int> PII;

int n, m;
int g[N][N];//存图 
int d[N][N];//存每个点 到起点的距离 

int bfs()
{
    queue< pair<int, int> > q;

    q.push({0, 0});

    memset(d, -1, sizeof(d));//初始化距离为-1,表示没有走过 

    d[0][0] = 0;//表示起点走过了 


    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    while (q.size())//队列不为空
    {
        PII t = q.front();//取队头元素

        q.pop();//出队

        for (int i = 0; i < 4; i++)//枚举4个方向 
        {
            int x = t.first + dx[i], y = t.second + dy[i];

			//x、y都在边界内,且这个点点之前没有走过 
            if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
            {
            	//当前点到起点的距离 
                d[x][y] = d[t.first][t.second] + 1;
                
                q.push({x, y});//新坐标入队
            }
        }
    }

    return d[n - 1][m -1];//输出右下角点 距起点的距离即可 
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> g[i][j];

    cout << bfs() << endl;

    return 0;
}

2、八数码

在这里插入图片描述
输入样例:
2 3 4 1 5 x 7 6 8
输出样例
19

三、树与图的深度优先遍历

1、树的重心

在这里插入图片描述
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4

四、树与图的广度优先遍历

1、图中点的层次

在这里插入图片描述
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1

五、拓扑排序

1、有向图的拓扑序列

在这里插入图片描述
输入样例:
3 3
1 2
2 3
1 3
输出样例:
1 2 3

六、Dijkstra

1、Dijkstra求最短路|

在这里插入图片描述
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3

2、Dijkstra求最短路||

在这里插入图片描述
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3

七、bellman-ford

1、有边数限制的最短路

在这里插入图片描述
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3

八、spfa

1、spfa求最短路

在这里插入图片描述
输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2

2、spfa判断负环

在这里插入图片描述
输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes

九、Floyd

1、Floyd求最短路

在这里插入图片描述
输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1

十、Prim

1、Prim算法求最小生成树

在这里插入图片描述
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6

十一、Kruskal

1、Kruskal算法求最小生成树

在这里插入图片描述
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6

十二、染色法判定二分图

1、染色法判定二分图

在这里插入图片描述
输入样例:
4 4
1 3
1 4
2 3
2 4
输出样例:
Yes

十三、匈牙利算法

1、二分图的最大匹配

在这里插入图片描述输入样例:
2 2 4
1 1
1 2
2 1
2 2
输出样例:
2

第四讲——数学知识(质数,约数,欧拉函数,快速幂,扩展欧几里得算法,中国剩余定理,高斯消元,求组合数,容斥原理,博弈论)

一、质数

二、约数

三、欧拉函数

四、快速幂

五、扩展欧几里得算法

六、中国剩余定理

七、高斯消元

八、求组合数

九、容斥原理

十、博弈论

第五讲——动态规划(背包问题,线性DP,区间DP,计数类DP,数位统计DP,状态压缩DP,树形DP,记忆化搜索)

一、背包问题

1、01背包问题

在这里插入图片描述
4 5
1 2
2 4
3 4
4 5
输出样例:
8

2、完全背包问题

在这里插入图片描述
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

3、多重背包问题|

在这里插入图片描述
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

4、多重背包问题||

在这里插入图片描述
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

5、分组背包问题

在这里插入图片描述
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

二、线性DP

1、数字三角形

在这里插入图片描述
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30

2、最长上升子序列

在这里插入图片描述
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4

3、最长上升子序列||

在这里插入图片描述
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4

4、最长公共子序列

在这里插入图片描述输入样例:
4 5
acbd
abedc
输出样例:
3

5、最短编辑距离

在这里插入图片描述
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4

6、编辑距离

在这里插入图片描述
输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3

三、区间DP

1、石子合并

在这里插入图片描述
输入样例:
4
1 3 5 2
输出样例:
22

四、计数类DP

1、整数划分在这里插入图片描述

五、数位统计DP

1、计数问题

在这里插入图片描述
输入样例:
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出样例:
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

六、状态压缩DP

1、蒙德里安的梦想

在这里插入图片描述输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205

2、最短Hamilton路径

在这里插入图片描述
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18

七、树形DP

1、没有上司的舞会

在这里插入图片描述
输入样例:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出样例:
5

八、记忆化搜索

1、滑雪

在这里插入图片描述
输入样例:
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出样例:
25

第六讲——贪心(区间问题,Huffman树,排序不等式,绝对值不等式,推公式)

一、区间问题

1、区间选点

在这里插入图片描述
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

2、最大不相交区间数量

在这里插入图片描述
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

3、区间分组

在这里插入图片描述
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

4、区间覆盖

在这里插入图片描述
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2

二、Huffman树

1、合并果子

在这里插入图片描述
输入样例:
3
1 2 9
输出样例:
15

三、排序不等式

1、排队打水

在这里插入图片描述
输入样例:
7
3 6 1 4 2 5 7
输出样例:
56

四、绝对值不等式

1、货仓选址

在这里插入图片描述
输入样例:
4
6 2 9 1
输出样例:
12

五、推公式

1、耍杂技的牛

在这里插入图片描述
输入样例:
3
10 3
2 5
3 3
输出样例:
2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值