week4 KMP+链表+栈+队列

一、KMP

next数组的作用:存储模式串每个位置的最长公共前后缀的长度是多少,之后在移动模式串指针的时候,当p[j]!=s[i]时(p模式串,s主串),指针j可以快速移动,即j=next[j]。
为什么?

 题目:​​​​​活动 - AcWing

#include<iostream>
#include<string>
using namespace std;
const int N=1e5+10,M=1e6+10;
int ne[N];
int main()
{
	int n,m;
	char p[N],s[M];
	cin>>n>>p+1>>m>>s+1;
	//求模式串p的next数组
	for(int i=2,j=0;i<=n;i++)
	{
		while(j&&p[i]!=p[j+1])
			j=ne[j];
		if(p[i]==p[j+1])
			j++;
		ne[i]=j;
	}
	//匹配,是从下标为1开始匹配的
	for(int i=1,j=0;i<=m;i++)
	{
		while(j&&s[i]!=p[j+1])
			j=ne[j];
		if(s[i]==p[j+1])//当前两个字符相等,都往前移动
			j++;
		if(j==n)//匹配成功
		{
			cout<<i-n<<' ';//题目要求下标从0开始
			j=ne[j];//下一次继续往后查找时,模式串要移动到的位置
		}
	}
	return 0;
}

二、单链表

每一个节点都有一个指向下一个与它相邻的节点的指针。

思路:开两个数组,val[],ne[]分别来存储每个下标idx对应的值和它指向的下一节点的位置,idx存的是每个节点的对应的下标,相邻的每个节点的idx不是连续的,每个节点要去访问它的下一个节点是通过当前节点的idx,即ne[idx]来得到它的下一个节点指向那的。

 模板:

#include<iostream>
using namespace std;
const int M=100;
int head,idx;
int val[M],ne[M];
void init()
{
	head=-1;//头结点
	idx=0;//头结点指向的下一个节点
}
//在头结点后面插入新的节点
void insert_head(int x)
{
	val[idx]=x;
	ne[idx]=head;//此时新插入的节点指向的下一节点是头结点原本指向的,现在头结点要指向它
	head=idx;//头结点指向当前节点
	idx++;
}
//将x插到下标k的后面
void insert_x(int k,int x)
{
	//当前插入节点的变幻,idx仅仅只是用来表示当前点的位置,不是下标表示,我们访问每一个
	//节点是通过ne数组来访问的,ne数组里存放了下一个节点的址idx(每个节点的idx都是不同的)
	//和值,
	val[idx]=x;
	ne[idx]=ne[k];
	//第k个节点指向的节点发生改变
	ne[k]=idx;
	idx++;
}
//将k点后面的节点删掉
void dele(int k)
{
	ne[k]=ne[ne[k]];
}
int main()
{
	int m,x,k;
	char ch;
	cin>>m;
	while(m--)
	{
		cin>>ch;
		init();//别忘了初始化
		if(ch=='H')
		{
			cin>>x;
			insert_head(x);
		}
		else if(ch=='D')
		{
			cin>>k;
			if(k==0)//删除头节点,即让头节点变为原本头节点指向的节点
				head=ne[head];
			dele(k-1);//k-1题目下标是从1开始的,我们是0开始的
		}
		else
		{
			cin>>k>>x;
			insert_x(k-1,x);
		}
	}
	for(int i=head;i!=-1;i=ne[i])//从第一个节点开始遍历,即头节点指向的下一个节点
	{
		cout<<val[i]<<' ';
	}
	return 0;
}

三、双链表

#include<iostream>
#include<string>
using namespace std;
const int M=1e5+10;
int val[M],l[M],r[M],idx;
void inti()
{
	l[1]=0;//头结点
	r[0]=1;//尾结点
	idx=2;//下标从2开始,因为0、1被作为左右端点了
}
//在第k个节点的右边插入数x,如果要求的是在K的左边插入新节点,传入的值为l[k],不能是k-1,因为下标并不是连续的
void insert(int k,int x)
{
	val[idx]=x;
	r[idx]=r[k];//新加入节点的指向的右节点
	l[idx]=k;//新加入节点的指向的左节点
	l[r[k]]=idx;//!!!顺序不能倒
	r[k]=idx;//k节指向的右节点变为新加入的节
	idx++;
}
//删除第k个点
void remove(int k)
{
	r[l[k]]=r[k];//让K的左右节点分别互指
	l[r[k]]=l[k];
}
int main()
{
	int m,x;
	string s;
	cin>>m;
	inti();
	while(m--)
	{
		cin>>s;
		if(s=="L")
		{
			cin>>x;
			insert(0,x);//在最左端插入数x
		}
		else if(s=="R")
		{
			cin>>x;
			insert(l[1],x);//在最右端插入,在尾结点后面插入
		}
		else if(s=="D")//将第k个插入的数删除
		{
			int k;
			cin>>k;
			remove(k+1);
		}
		else if(s=="IL")
		{
			int k;
			cin>>k>>x;
			insert(l[k+1],x);//在第k个插入的数的左侧插入一个数,传入的是l[k]!!!!
		}
		else
		{
			int k;
			cin>>k>>x;
			insert(k+1,x);
		}
	}
	for(int i=r[0];i!=1;i=r[i])
		{
			cout<<val[i]<<' ';
		}
		cout<<endl;
	return 0;
}

四、栈

基本特点:先进后出

几个操作:
//插入
int x;
stk[++tt]=x;
//从栈顶弹出
tt--;
//判断是否为空
if(tt>0)
	not empty;
else
	empty;
//取栈顶元素
stk[tt];

模拟栈:

#include<iostream>
#include<string>
using namespace std;
const int N=1e5+10;
int stk[N],tt=-1;//下标从0开始
int main()
{
	int m,x;
	string str;
	cin>>m;
	while(m--)
	{
		cin>>str;
		if(str=="push")
		{
			cin>>x;
			//向栈顶插入元素
			stk[++tt]=x;
		}
		else if(str=="pop")
			tt--;//从栈顶弹出一个元素
		else if(str=="empty")
		{
			if(tt<0)
				cout<<"YES"<<endl;
			else
				cout<<"NO"<<endl;
		}
		else
		{
			//查询栈顶元素
			cout<<stk[tt]<<endl;
		}
	}
	return 0;
}

3302. 表达式求值 - AcWing题库
思路:模拟两个栈,一个来存储数字,一个来存储操作符,遇到操作符的时候,先判断当前操作符与栈顶操作符的优先级,如果小于或等于的话,先把栈顶的运算符运算了,在让当前运算符入栈,否则,直接入栈。对于括号,我们把括号的优先级置为最低,当栈顶为'('时,下一个运算符的优先级肯定大于'(',所以直接让这个运算符入栈,之后的计算和上面的一样,直到遇到')'时,就可以把括号里的值都计算出来,让'('出栈。

#include<iostream>
#include<string>
#include<map>
using namespace std;
const int N=1e5;
int num[N],t_num=-1,t_op=-1;;
char op[N];
map<char,int> h={{'+',1},{'-',1},{'*',2},{'/',2}};//方便比较运算符的优先级,没有设置的默认值是0
void eval()
{
	int a=num[t_num--];
	int b=num[t_num--];
	char ch=op[t_op--];
	int res=0;
	//这里注意a,b的运算顺序,元素进入栈里是逆着进的,但我们运算的时候要从前往后算
	if(ch=='+')
		res=b+a;
	else if(ch=='-')
		res=b-a;//不能是a-b!!!
	else if(ch=='*')
		res=b*a;
	else
		res=b/a;
	num[++t_num]=res;//将结果入栈
}
int main()
{
	string str;
	cin>>str;
	int len=str.length();
	for(int i=0;i<len;i++)
	{
		if(isdigit(str[i]))
		{
			int temp=0,j=i;
			while(j<len&&isdigit(str[j]))
			{
				temp=temp*10+str[j]-'0';
				j++;
			}
			num[++t_num]=temp;//数字入栈
			i=j-1;
		}
		else if(str[i]=='(')
		{
			op[++t_op]='(';
		}
		else if(str[i]==')')//遇到右括号了,把里面的值都先给计算了
		{
			while(op[t_op]!='(')
				eval();
			t_op--;//将左括号出栈
		}
		else
		{
			while(t_op>=0&&h[op[t_op]]>=h[str[i]])
				eval();
			op[++t_op]=str[i];//优先级高的直接入栈
		}
	}
	while(t_op>=0)
		eval();//将最后剩余的数进行计算
	cout<<num[t_num]<<endl;
	return 0;
}

五、队列

模拟队列

#include<iostream>
#include<string>
using namespace std;
const int N=1e5+10;
int q[N],hh,tt=-1;
int main()
{
	int m,x;
	string s;
	cin>>m;
	while(m--)
	{
		cin>>s;
		if(s=="push")
		{
			cin>>x;
			q[++tt]=x;//向队尾插入一个元素
		}
		else if(s=="pop")//从对头弹出一个数
			hh++;
		else if(s=="empty")
		{
			if(hh>tt)
				cout<<"YES"<<endl;
			else
				cout<<"NO"<<endl;
		}
		else
		{
			cout<<q[hh]<<endl;//查询对头元素
		}
	}
	return 0;
}

单调栈:元素从队尾进和队尾出,栈里的元素是单调性的。
应用:找在x左边第一个比x小的数。
思路:每次在加元素前,不断查找元素左边比它大或等于它的数弹出去(因为我们查找的是左边第一个比它小的数,所以所有比新加入元素大的数都没有必要与下一个新加入元素去比较),最后如果存在比它小的数输出这个数,然后自己再入栈。

#include<iostream>
using namespace std;
const int N=1e5+10;
int stk[N],tt,n;
int main()
{
	cin>>n;
	int x;
	for(int i=0;i<n;i++)
	{
		cin>>x;
		//判断在当前输入的数之前是否存在比它更小的,若有输出该数,否则把输入的数入栈
		while(tt&&stk[tt]>=x)//把比x大的数弹出栈
			tt--;
		if(tt&&stk[tt]<x)
			cout<<stk[tt]<<' ';
		else
			cout<<-1<<' ';
		stk[++tt]=x;
	}
	return 0;
}

单调队列:指队头和队尾都可以进行出队操作,但只有队尾可以进行入队操作,队列里所有的数是单调性排列的,递增或递减。
应用:滑动窗口,每次输出当前窗口范围里所有数的最小值和最大值。
思路:先判断对头的数要不要先出队(该数可能不在窗口范围里了),在把新框入的数加入之前先从队尾把比该数大的数给弹出,最后输出对头对应的值。从尾巴开始把比x大的数弹出(下一个窗口的最小值只会在x和新加入的值之间产生),最后不管如何x要从后面加入,因为对于后面x有被框选进的窗口,我们是还不知道x在那个窗口里会不会是最小值。

#include<iostream>
using namespace std;
const int N=1e6+10;
int n,k,a[N],q[N];//q里面存放的是对应值的下标
int main()
{
	int tt=-1,hh=0;
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	for(int i=0;i<n;i++)
	{
		//先判断队头要不要出队
		if(hh<=tt&&i-k+1>q[hh])
			hh++;//队头出队
		while(hh<=tt&&a[q[tt]]>=a[i])
			tt--;
		q[++tt]=i;//要先把当前值的下标入队,因为它有可能刚好是最小值,之前的队列被清空了
		if(i-k+1>=0)//要先特判一下,当框选到k个数时才开始输出最小值
			printf("%d ",a[q[hh]]);
	}
	printf("\n");
	//最大值
	tt=-1,hh=0;
	for(int i=0;i<n;i++)
	{
		if(hh<=tt&&i-k+1>q[hh])//i-k+1是滑动窗里第一个数的下标
			hh++;
		while(hh<=tt&&a[q[tt]]<=a[i])
			tt--;
		q[++tt]=i;
		if(i-k+1>=0)
			printf("%d ",a[q[hh]]);
	}
	printf("\n");
	return 0;
}

思维题:

1、Problem - B - Codeforces (Unofficial mirror site, accelerated for Chinese users)
题意:删除尽可能多的数,使剩下的是非质数。(题目保证了一定存在)
思路:对于含有1,4,6,8,9的数,我们一定可以只留下其中的一个;否则就只能由3,5,7
这三个数来组成,对于长度大于3的,如果3,5,7同时都存在的话,那么这三个数一定可以组成
两位数的非素数;如果三个数只存在1个或2个,那么一定会有重复的两位数,也是属于非素数,
所以只需要判断由他们组成的任意两个数是否是非素数即可 。

#include<iostream>
const int M=55;
using namespace std;
int Isprime(int x)
{
	for(int i=2;i*i<=x;i++)
	{
		if(x%i==0)
			return 0;
	}
	return 1;
}
int main()
{
	int t,k,a[M],ans,sum;
	scanf("%d",&t);
	while(t--)
	{
		int flag=0;
		scanf("%d",&k);
		for(int i=0;i<k;i++)
		{
			scanf("%1d",&a[i]);
			//如果存在数字1,4,6,8,9中的一种,那么肯定最后可以只留下这一个数字
			if(!flag)
			{
				if(a[i]==1||a[i]==4||a[i]==6||a[i]==8||a[i]==9)
				{
					sum=1;
					ans=a[i];
					flag=1;
				}
			}
		}
		if(!flag)
		{
			for(int i=0;i<k;i++)
			{
			
				//否则的话从剩下的数中查找任意两个数组成的是非质数的
				for(int j=i+1;j<k;j++)
				{
					int num=a[i]*10+a[j];
					if(!Isprime(num))
					{
						sum=2;
						ans=num;
						flag=1;
						break;
					}
				}
				if(flag)
					break;
			}
		}
		printf("%d\n%d\n",sum,ans);
	}
	
	return 0;
}

 2、 B. Shifting Sort 
思路:遍历所有的元素,每次查找第i个元素之后所有元素中值最小的(下标记为idx),把[i,idx]里的元素全都往后移一位,把这个最小的元素移到第i个元素的位置。经过这样的操作之后,数组里从i往后的所有元素中最小的元素会被移到前面了。所以总的思路是:让整个数组里小的元素一一先出去。
 

#include<iostream>
#include<vector>
#include<utility>
using namespace std;
typedef pair<int,int> pii;
int main()
{
	int t,n;
	cin>>t;
	while(t--)
	{
		cin>>n;
		vector<int>a(n+1);
        vector<pii>action;
		int sum=0;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
		}
		for(int i=1;i<n;i++)
		{
			int min_pos=i;//存放最小元素的下标
			for(int j=i+1;j<=n;j++)//查找i之后的元素中最小的值
			{
				if(a[min_pos]>a[j])
					min_pos=j;
			}
			if(min_pos>i)
			{
				action.push_back({i,min_pos});
				//把最小的值移到[i,min_pos]这个区间的最前面,其它的值往后移一位
				int temp=a[min_pos];
				for(int k=min_pos;k>i;k--)
				{
					a[k]=a[k-1];//往后移
				}
				a[i]=temp;
			}
		}
		cout<<action.size()<<'\n';
		for(auto &lr:action)
		{
			cout<<lr.first<<' '<<lr.second<<' '<<lr.second-lr.first<<'\n';
		}
	}
	return 0;
}

E1. Permutation Minimization by Deque
题意:将一组整数每次只能取该组数的第一个数,取出来后放到新的一组数里,只能放在头或尾这两个位置,求能组成的最小字典序数组。
思路:利用deque容器来存放,每次比较当前数组的第一位和已经取出来放到另一个容器的第一位数的大小,若小于,则把该数放到容器的首位q.push_front(x),否则放到末尾q.push_back(x)。

#include<iostream>
#include<deque>
using namespace std;
const int M=2e5+10;
deque<int> q;
int main()
{
	int t,n;
	int p[M];
	cin>>t;
	while(t--)
	{
		cin>>n;
		q.clear();
		for(int i=0;i<n;i++)
		{
			cin>>p[i];
		}
		int head=p[0];
		q.push_front(head);
		for(int i=1;i<n;i++)
		{
			if(p[i]<head)
			{
				q.push_front(p[i]);
				head=p[i];
			}
			else
			{
				q.push_back(p[i]);
			}
		}
		for(deque<int>::const_iterator iter=q.begin();iter!=q.end();iter++)
			cout<<*iter<<" ";
		cout<<endl;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值