算法笔记&PAT总结

文章目录

    • Error&Trick
    • 静态链表
    • 数学问题
      • 分数的四则运算
      • 素数
      • 质因子分解
      • 大整数运算
    • 排序
      • 插入排序
      • 归并排序
      • 堆排序
    • 搜索
      • DFS
      • BFS
      • 二叉树的遍历
      • 多个结点
      • BST
      • 并查集
      • AVL树
      • 图的遍历
      • 最短路
    • 动态规划
      • 最长回文子串
      • 01背包问题
    • 拓展
      • 分块思想
      • 树状数组
    • 题目

Error&Trick

  • 不要过分追求简洁的代码和尽量少的变量,很可能会导致出错,不同数组的下标就用不同变量表示。
  • sort可以对vector< vector <>>排序,其规则和vector的比较规则一致,也和题目给出的序列大小比较的定义一致。由第一个不相等的数决定相对大小,完全一致则相等。如果v1和v2长度不一样,共有元素都一致,长度长的大,相当于在短序列后补-inf。string的比较规则和vector一致。
  • 运行超时:字符数组单个输出改成一串整个输出
  • 每次做树和图的问题,好好考虑,树实际上是特殊的图,但是图从树的角度来看,说不定解题也更简单
  • 题目如果要求多个序列按递增或递减顺序输出,那么可以在处理前,对数据做处理,使得满足要求序列出现顺序就是输出顺序。或者用sort对vector排序。
  • in the range of long int: 使用long long int,不然可能会答案错误或者浮点错误!
  • scanf按格式输入经常可以用于转化时间的HH:MM:SS形式,scanf("%d:%d:%d", h,m,s); time=hx3600+mx60+s;或者其他有固定输入格式,而题目又需要取出整数的情况。
  • 数字字符串的处理 stoi() 和 itos(): 在头文件cstring中,atoi()的参数是 const char* ,因此对于一个字符串str我们必须调用 c_str()的方法把这个string转换成 const char*类型的,而stoi()的参数是const string *,不需要转化为 const char *。
  • 细节错误:循环中不小心把j写成了i导致结果出错;vector的erase使用导致序列长度发生变化,这样会使得循环下标超过上限,且不能遍历每个位置。
  • 转化数字时,N的范围是0~19,然后给的例子都是10以内的数,所以就想当然的直接c-'0’当成单个字符处理了,善用stoi和itos函数

静态链表

1032 Sharing
1052 Linked List Sorting
1074 Reversing Linked List
1097 Deduplication on a Linked List
模板:注意链表为空时输出的处理方式,题目没有明说,就试试两种,一种不输出,另一种输出-1

struct Node{
	int add,key,next;
	XXX 其他性质
}node[1000005];

//确定在链表上的节点,清除无效结点
vector v;
while(head!=-1){
	v.push_back(node[head]);
	head=node[head].next;
}

//输出结果格式:add key next,且最后一个结点next为-1,特殊处理
void print(const vector& v){
    int n=v.size();
    //if(n==0)  cout<<-1<0){
        for(int i=0;i

数学问题

1100 Mars Numbers
最大公约数:gcd(a,b)=gcd(b,a%b);
最小公倍数:d=gcd(a,b),a/d*b(避免相乘后溢出)

分数的四则运算

1081 Rational Sum
1088 Rational Arithmetic
注意:保证分母为正数,否则分子分母都取相反数
模板:约分在计算完成后进行,简化计算的方法—分子为0,那分母取1

//针对输入为a/b形式的,其中b>0,但可以是未约分的形式
void print(int a,int b){
    if(a==0){
        cout<<0;
        return;
    }
    int t=gcd(abs(a),b);
    a/=t;b/=t;
    if(b==1)
        printf("%d",a/b);
    else if(abs(a)>b)
        printf("%d %d/%d",a/b,abs(a)%b,b);
    else printf("%d/%d",a,b);
}

素数

模板:注意特殊情况1

bool isPrime(int n){
	if(n<=1) return false;
	int x=(int)sqrt(1.0*n);    
	for(int i=2;i<=x;i++)
		if(n%i==0) return false;
	return true;
}

素数表:用素数筛法优化,注意此时上界为maxn,不是sqrN

const int maxn=1000001;
int p[maxn],prime[maxn],pNum=0;
void Find_Prime(){
	for(int i=2;i

质因子分解

1059 Prime Factors
一个正整数n的质因子:质因子全部<=sqrt(n)或者是一个>sqrt(n)(有可能是n本身)
模板:在素数表的基础上,枚举素因子

struct factor{
	int x,cnt;
}fac[10];

//枚举1~sqrt(n)范围内的所有质因子
    for(int i=0;i

这个问题等价于求正整数n的因子个数

大整数运算

1023 Have Fun with Numbers
1024 Palindromic Number
1065 A+B and C (64bit)

  • 结构体

整数的高位对应数组的高位,整数的低位对应数组的低位

struct bign{
	int d[1000];
	int len;
	bign(){
		fill(d,d+1000,0);
		len=0;
	}
};

bign change(char str[]){//将整数转化为bign
	bign a;
	a.len=strlen(str);
	for(int i=0;i
  • 比较
int compare(const bign& a,const bign& b){
	if(a.len>b.len) return 1;
	else if(a.len=0;i--) //从高位开始比较
			if(a.d[i]>b.d[i]) return 1;
			else if(a.d[i]
  • 加减法:针对非负整数,否则将负号去掉,转化为相反的运算
bign add(const bign& a,const bign& b){
	bign c;
	int carry=0;//carry表示进位
	for(int i=0;i=1&&c.d[c.len-1]==0) //去除高位的0,且至少保留一位
		c.len--;
	return c;
}

排序

1089 Insert or Merge
1098 Insertion or Heap Sort

  • 其实都可以借助sort完成

插入排序

void insertSort(){
	for(int i=1;i0&&A[j-1]>tmp){
			A[j]=A[j-1];
			j--;
		}
		A[j]=tmp;
	}
}

归并排序

注意非递归写法中边界条件为step/2

const int maxn = 1e5;
int A[maxn];
//将数组A[l1,r1]和A[l2,r2]合并成一个有序数组
void merge(int A[], int l1, int r1, int l2, int r2){
	int i=l1, j=l2, temp[maxn], index=0;
	while(i<=r1 && j<=r2){
		if(A[i]<=A[j]) temp[index++]=A[i++];
		else temp[index++]=A[j++];
	}
	while(i<=r1) temp[index++]=A[i++];
	while(j<=r2) temp[index++]=A[j++];
	for (int i = 0; i < index; ++i)
	{
		A[l1+i] = temp[i];
	}
}
// left=0, right=n-1
void mergeSort(int A[], int left, int right){
	if(left

堆排序

取出堆顶元素,将最后元素放至堆顶,并调整,循环至堆中只有一个元素为止。

//倒着遍历数组,最后能够实现递增排序
void heapSort(){
	createHeap();
	for(int i=n;i>1;i--){
		swap(heap[i],heap[1]);
		downAdjust(1,i-1);
	}
}

搜索

1091 Acute Stroke

DFS

1068 Find More Coins
问题:给定一个序列,枚举这个序列的所有子列(可以不连续)。变形为:选择一个序列中满足要求的不连续子序列。 类比二叉树,每个结点选不选是2个分支。
模板:注意DFS()可以判断是否需要剪枝,一般先进行最优条件的判断再剪枝,否则可能会失去边界情况的分,比如最后一个数要被考虑的情况,此时sum这些数据记录的暂时是上一个index的情况。

vector ans,tmp; //tmp存放临时最优子序列
void DFS(int index,int XXX,int best){ //XXX为题目要求,best为最优子序列的判断标准
	if(XXX满足要求) {
		if(最优子序列是否需要更换){
			best=
			ans=tmp;
		}
		return;
	}
	if(index==n||XXX不可能再满足要求) return;
	//选择index号数
	tmp.push_back(A[index]);
	DFS(index+1,XXX+,best+); //更新XXX和best
	tmp.pop_back();  //注意这一步!
	//不选index号数
	DFS(index+1,XXX,best);
}

注意:DFS选择路径时,一般选择路径1后,会做一个复原处理,eg: tmp.pop_back(),即恢复到不选路径1的状态!多个结点的情况看树的先根遍历部分。

BFS

1021 Deepest Root
1079 Total Sales of Supply Chain
1090 Highest Price in Supply Chain
1094 The Largest Generation
1106 Lowest Price in Supply Chain

问题:树的层序遍历及变形问题,要求区别每一层
模板:

void BFS(int s){
	queue q;
	q.push(s);
	int cnt=1;//根结点这一层的结点数
	num=1,level=0/1;//记得num初始化为1,否则边界情况--只有根结点会报错,level根据题目定
	while(!q.empty()){
		if(cnt==0){//当前已经是新的一层了
			level++;
			cnt=num=q.size();
		}
		取出并访问q.top()
		q.pop();
		cnt--;
		将top的下一层未入队的节点全部入队,并设置为已入队,标记它们的层号为now的层号+1
	}
}

Note:DFS等于于树的先根遍历,BFS等价于树的层序遍历。用DFS、BFS解决的问题有时候可以转为对树的直观求解。

二叉树的遍历

1020 Tree Traversals
1064 Complete Binary Search Tree
1086 Tree Traversals Again
1119 Pre- and Post-order Traversals
问题:根据先/后序遍历和中序遍历重建树
问题变形:根据中序遍历的对应的入栈出栈序列输出后序遍历结果。(题目暗示)
所有的入栈元素顺序构成的序列为树的先序遍历序列;所有的出栈元素顺序构成的序列为中序遍历序列。

node* CreateTree(int preL,int preR,int inL,int inR){
	if(preL>preR)   return NULL;
	node* root=new node;
	root->data=pre[preL];
	root->lchild=root->rchild=NULL;
	int k;
	for(k=inL;k<=inR;k++)
		if(pre[preL]==ins[k]) break;//尽量避免用in,可能会和头文件重定义
	int n=k-inL;
	root->lchild=CreateTree(preL+1,preL+n,inL,k-1);
	root->rchild=CreateTree(preL+n+1,preR,k+1,inR);
	return root;
}
  • 完全二叉树的性质:将它的层序遍历存储在一维数组中,父结点的数组下标若是i,则左孩子的下标是2i+1,右子结点的下标是2i+2。

多个结点

1053 Path of Equal Weight
模板:一般用静态表示,即child记录数组下标

struct node{
	typename data;
	vector child;
 };

如果题目不涉及数据域,那就简化成vector< int > child[maxn],实际上也是邻接矩阵的表达式。

  • 树的遍历
    模板:先根遍历的边界情况由for循环已经删去了
//先根遍历
void preorder(int root){
	ans.push_back(node[root].data);
	for(int i=0;i q;
	q.push(root);
	while(!q.empty()){
		int front=q.front();
		q.pop();
		ans.push_back(node[front].data);
		for(int i=0;i

先根遍历的应用例子:一般会有题目的条件筛选路径,直接将选择指标写在参数表里,注意回溯到上一层中要将前面加入的子结点pop_back()。注意下面这种写法,其实舍去了对最开始的第一个节点的处理,如果起始节点一定确定,图可以考虑用这个方法或者每次对起始节点做处理。

void DFS(int index,int sum){
    if(sum>s) return;
    if(sum==s){
        if(T[index].child.size()!=0) return;
        ans.push_back(tmp);
        return;
    }
    for(int i=0;i

BST

1043 Is It a Binary Search Tree
1064 Complete Binary Search Tree
1099 Build A Binary Search Tree
1110 Complete Binary Tree
1115 Counting Nodes in a BST

BST的性质:BST的中序遍历是有序的。

  • 建树:不断插入新节点
void insert(node*& root,int x){
	if(root==NULL){
		root=new node;
		root->data=x;
		root->lchild=root->rchild=NULL;
		return;
	}
	if(x==root->data) return;  //查找成功,节点已存在,不插入
	else if(xdata) 
		insert(root->lchild,x);
	else insert(root->rchild,x);
}
node* CreateTree(int data[],int n){
	node* root=NULL;
	for(int i=0;i
  • 删除:查找结点左子树中的最大节点(rchild)或右子树的最小节点(lchild)
node* findMax(node* root){
	while(root->rchild!=NULL)
		root=root->rchild;
	return root;
}
node* findMin(node* root){
		while(root->lchild!=NULL)
			root=root->lchild;
		return root;
}
void delete(node*& root,int x){
	if(root==NULL)  return;
	if(root->data==x){//找到欲删除结点
		if(root->lchild!=NULL&&root->rchild!=NULL){
			node* pre=findMax(root->lchild);
			root->data=pre->data;
			delete(root->lchild,pre->data);  //在左子树中删除pre
		}
		else{
			if(root->lchild==NULL) root=root->rchild;
			if(root->rchild==NULL) root=root->lchild;
		}
	}
	else if(xdata)
		delete(root->lchild,x);
	else delete(root->rchild,x);
}

并查集

1034 Head of a Gang
1114 Family Property
1118 Birds in Forest
常用于计算连通分量个数
问题:给定结点和边,最少添加几条边使结点间都连通。例:建公路连通城市。

注意:为什么路径压缩查询两个点是否在同一棵树上,仍然要使用findroot(),而不是father[]直接记录唯一的根结点?
因为每次合并可能使得一棵树的根结点a的father变成b,此时这棵树的根结点更新成了b,但是所有原来以a为根结点的结点对应仍为a。

//初始化,每个结点都是单独的集合,根节点可以用来存储集合个数。
for(int i=1;i<=n;i++)
	par[i]=i;
	
//查找根节点
int find(int x){
	while(far[x]!=x)
		x=par[x];
	return x;
}

//合并
void Union(int a,int b){
	int pa=find(a);
	int pb=find(b);
	if(pa!=pb) 
		par[pa]=pb;
}
  • 路径压缩
int find(int x){
	int a=x;
	while(par[x]!=x) 
		x=par[x];
		//x存放根节点,将路径中经过的点的par改成x
	while(par[a]!=a){
		int tmp=a;
		a=par[a];
		par[tmp]=x;
	}
	return x;
}

int find(int x){
	if(x==par[x]) return x;
	else{
		int v=find(par[x]);
		par[x]=v;
		retuen v;
	}
}
  • 根结点数组
//用于计算分量个数
for(int i=1;i<=n;i++)
	isRoot[par[i]]=true;
for(int i=1;i<=n;i++)
	ans+=isRoot[i];

//用于求每个集合中元素个数
for(int i=1;i<=n;i++)
	isRoot[par[i]]++;

AVL树

1066 Root of AVL Tree
1123 Is It a Complete AVL Tree
注意初始化新结点时,不要忘记对height初始化为1

struct node{
	int v,height;
	node *lchild,*rchild;
};
//根结点的高度为1,空结点的高度为0
int getHeight(node* root){
	if(root==NULL) return 0; 
	else return root->height;
}
int getBalance(node* root){
	return getHeight(root->lchild)-getHeight(root->rchild);
}
void updateHeight(node* root){
	root->height=max(getHeight(root->lchild),getHeight(root->rchild))+1;
}
  • 旋转
//Left Rotation
void L(node*& root){
	node* temp=root->rchild;
	root->rchild=temp->lchild;
	temp->lchild=root;
	updateHeight(root); //更新结点高度
	updateHeight(temp);
	root=temp;
}

//Right Rotation
void R(node*& root){//和RL左右都相反
	node* temp=root->lchild;
	root->lchild=temp->rchild;
	temp->rchild=root;
	updateHeight(root); //更新结点高度
	updateHeight(temp);
	root=temp;
}
  • 插入: 调整最靠近插入结点的失衡结点

LL:左孩子结点平衡因子是1,Right Rotation
LR:左孩子结点平衡因子是-1,先对左孩子结点做Left Rotation转化成LL型
RR:右孩子结点平衡因子是-1,Left Rotation
RL:右孩子结点平衡因子是1,先对右孩子结点做Right Rotation转化为RR型

void insert(node*& root,int x){
	if(root==NULL){
		root=newNode(x);
		return;
	}
	if(xv){
		insert(root->lchild,x);
		updateHeight(root);
		if(getBalance(root)==2){
			if(getBalance(root->lchild)==1) //LL型
				R(root);
			else if(getBalance(root->lchild)==-1){//LR型
				L(root->lchild);
				R(root);
			}
		}
	}
	else{
		insert(root->rchild,x);
		updateHeight(root);
		if(getBalance(root)==-2){
			if(getBalance(root->rchild)==-1) //RR
				L(root);
			else if(getBalance(root->rchild)==1){//RL
				R(root->rchild);
				L(root);
			}
		}
	}
}
  • 建树
node* Create(int data[],int n){
	node* root=NULL;
	for(int i=0;i

堆是一棵完全二叉树,可以用数组存储

  • 给定一个序列建堆
//对heap[low,high]进行向下调整
void downAdjust(int low,int high){
	int i=low,j=i*2;
	while(j<=high){
		if(j+1<=high&&heap[j+1]>heap[j])
			j++;
		if(heap[j]>heap[i]){
			swap(heap[j],heap[i]);
			i=j;
			j=i*2;
		}
		else break;
	}
}
//完全二叉树叶子节点个数为n/2+1,[1,n/2]都是非叶子节点
void CreateHeap(){
	for(int i=n/2;i>=1;i--)
		downAdjust(i,n);
}
  • 删除与添加元素
//将最后元素覆盖堆顶,并调整
void deleteHeap(){
	heap[1]=heap[n--];
	downAdjust(1,n);
}

//将添加元素放在最后并向上调整
void upadjust(int low,int high){
	int i=high,j=i/2;
	while(j>=low){
		if(heap[j]

  • 邻接链表存储:实际上就是存储的边
vector Adj[N];

//如果还需要存储边权
struct node{
	int w,v;
	node(int a,int b){
		w=a;v=b;
	}
};
vector Adj[N];
Adj[1].push_back(node(3,4));  //利用构造函数可以避免使用临时变量

图的遍历

1034 Head of a Gang
1076 Forwards on Weibo
1107 Social Clusters
1114 Family Property

  • BFS和DFS常用于计算连通分量个数
  • DFS适合一些题目有额外条件的,BFS适合类似层序遍历的
void DFS(int u,int depth){
	vis[u]=true;
	//如果需要对u做一些操作,可以在这进行
	for(u的所有邻接点v)
		if(vis[v]==false) 
			DFS(v,depth+1); 
}
void BFS(int u){
	queue q;
	node start;
	start.v=s;
	start.layer=0;
	q.push(start); //入队后就改vis,后面也是
	vis[u]=1;
	while(!q.empty){
		node tmp=q.front();
		q.pop();
		int u=tmp.v;
		for(u的所有邻接点w){
			w.layer=tmp.layer+1;
			if(vis[w.v]==false){
				q.push(w);
				vis[w.v]=1;
			}
		}
	}
}
void trave(){
	int cnt=0;
	for(int u=0;u

最短路

1003 Emergency
1030 Travel Plan
1072 Gas Station
1087 All Roads Lead to Rome
1111 Online Map

  • Dijkstra算法
    问题变形:某两个城市间的最短路,并且要求路线上累计的权值最大,统计最短路的条数,输出路线。

动态规划

最长回文子串

1040 Longest Symmetric String
令dp[i][j]表示s[i]至s[j]是否为回文子串

  • s[i]==s[j],dp[i][j]=dp[i+1][j-1];
  • s[i]!=s[j],dp[i][j]=0

边界为:dp[i][i]=1,dp[i][i+1]=(s[i]==s[i+1])?1:0

    for(int i=0;i

01背包问题

1068 Find More Coins
有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包中,使得背包内的物品总价值最大,其中每件物品都只有一件。

令dp[i][v]表示前i件物品恰好装入容量为v的背包中获得的最大价值

  • 放入第i件物品,dp[i][v] = dp[i-1][v-w[i]]+c[i]
  • 不放入第i件物品, dp[i][v]=dp[i-1][v]
for(int i=1;i<=n;i++)
	for(int v=w[i];v<=V;v++)
		dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);

注意此处dp[i][v]中的v其实是v=V向w[i]变化,而且dp[i-1]只影响dp[i],为节省空间,可以用滚动数组

for(int i=1;i<=n;i++)
	for(int v=V;v>=w[i];v--)
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);

拓展

分块思想

1057 Stack
问题:在线查询一个序列第K大的数。
hash数组table[N]表示整数x的存在个数,其中N为元素范围+1,将元素分为M= ceil (sqrt(N))组,每块元素不超过S=floor(sqrt(N))个,统计数组block[M]表示每组元素的个数。

  • 插入或删除元素
//插入元素n
block[n/S]++; table[n]++;
//删除元素
block[n/S]--; table[n]--;
  • 查找序列中第k大的元素
    先利用block[]找到第K大元素所在块数,再利用table[]在块内找到这个元素
    block[i]: 对应元素值[ i*S,(i+1)*S-1 ]

树状数组

题目

1021 Deepest Root
1026 Table Tennis
1034 Head of a Gang
1057 Stack
1064 Complete Binary Search Tree
1068 Find More Coins
1087 All Roads Lead to Rome
1091 Acute Stroke
1095 Cars on Campus
1119 Pre- and Post-order Traversals

你可能感兴趣的:(算法笔记&PAT总结)