Check Inorder Traversals(前序、中序、后序的综合应用)

题目链接:题目详情 - 7-14 Check Inorder Traversals (pintia.cn)

样例1输入: 

4
1 2 3 4
2 4 3 1
4
2 1 3 4
1 2 3 4
2 1 4 3
2 1 5 6

样例1输出:

Yes
No
Yes
No

样例2输入:

7
1 2 3 4 6 7 5
6 7 5 4 3 2 1
3
2 1 6 4 7 3 5
2 1 6 7 4 3 5
2 3 1 7 4 5 6

样例2输出:

Are you kidding me?
2 6 7 4 5 3 1
2 7 6 4 5 3 1
You must be kidding me!

中文题意:

6 Check Inorder Traversals
假设二叉树中结点的值为互不相同的正整数。给定后序遍历序列和中序遍历序列,或者给定前序遍历序列和中序遍历序列,可以唯一地确定一棵二叉树。但是,若只给定后序和前序遍历序列,对应的二叉树可能不唯一。
现在给定后序和前序遍历序列,请你检查一组中序遍历序列,判断它们是否为给定后序、前序序列对应二叉树的中序遍历序列。

输入格式:
每个输入文件包含一个测试用例。第一行是一个正整数N(≤30),表示二叉树中的结点数量。第二行是前序遍历序列,第三行是后序遍历序列。

接下来一行是正整数K(≤30),然后是K行中序遍历序列。每个序列包含N个正整数,由空格间隔。

输出格式:
对于每一个中序遍历序列,如果它是对应树的中序遍历序列,在一行中输出Yes,否则输出No。 另一方面,有这样的可能:给定的前序和后序遍历序列不能形成一棵树。若是这种情况,在第一行打印Are you kidding me?,然后对于每个中序遍历序列,尝试使用给定的前序遍历序列构建一棵树,并在一行中输出这棵树的正确的后序遍历序列;如果这依然不能形成一棵树,则输出You must be kidding me!

分析:这道题目还是比较复杂的,首先我们需要判断根据给定的前序遍历序列和后序遍历序列能否构造出二叉树,如果能,那么我们就再每次根据所给定的中序遍历和前序遍历序列来唯一地确定一棵二叉树并对应的求出后序遍历序列,然后与给定的后序遍历序列进行比较,前序遍历和后序遍历无法确定二叉树,那么就直接按照前序遍历和中序遍历进行构造二叉树并输出后序遍历序列即可,题意中已经明确的说出了输出形式,这里就不赘述了。关键在于如何根据前序遍历和后序遍历判断能否构成二叉树。

首先我们可以知道:

前序遍历搜索顺序   根  (左子树根,左子树体)  (右子树根,右子树体)

后序遍历搜索顺序(左子树体,左子树根)  (右子树体,右子树根)   根

假如现在先序遍历序列是 a1,a2,……,an,后序遍历序列是c1,c2,……cn

那么显然应该有a1=cn,因为根是肯定存在的

然后这个时候对应着四种情况,一种是左右子树均有,一种是只有左子树,还有一种是只有右子树,最后一种情况就是左右子树均没有。

首先对于左右子树都没有的情况好处理,直接返回就好了,下面看看其他情况

不妨假设左子树有,右子树没有,那么搜索顺序变为

前序遍历搜索顺序   根  (左子树根,左子树体)

后序遍历搜索顺序(左子树体,左子树根)  根

这个时候肯定有a1+1=cn-1

同理假设左子树没有,右子树有,那么搜索顺序变为

前序遍历搜索顺序   根  (右子树根,右子树体)

后序遍历搜索顺序(右子树体,右子树根)   根

同理也有a1+1=cn-1

如果左右子树均有,那么a1+1对应左子树根,cn-1对应右子树根,那么两者是肯定不能相同的,所以我们就可以根据这个条件来判断左右子树是否均存在

也正是因为只存在左子树和只存在右子树是相同的判断条件,所以才会出现前序遍历和后序遍历无法唯一确定二叉树的情况。

按照上面这个方法我们就可以找到对应的子树区间并进行相应建树,如果能够找到解,说明根据所给定的前序遍历和后序遍历就可以建造出来二叉树。

至于根据前序遍历和中序遍历建造二叉树,这个比较简单,不懂的小伙伴可以看下我之前的博客:(2条消息) (L2-011)玩转二叉树(利用个给定序列建树)_给定序列如何构造二叉搜索树_AC__dream的博客-CSDN博客

下面我来说一下这道题的一些小细节:首先他给定的序列并不是排列,所以可能不连续,也可能出现重复元素,对于不连续的情况我们直接离散化一下就行,但是对于出现相同的元素的情况我们直接利用这个条件做出相应判断就行,就没必要再利用这个序列来进行建树了。最后一个测试点就会出现重复元素,利用一个map判断一下就好,如果要是出现了重复元素直接输出NO。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e6+10;
int a[N],b[N],c[N],ans[N];
vector<int> alls;
int find(int x)
{
	return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int n;
int mp1[N],mp2[N];
int mp[N];
bool build_13(int l1,int r1,int l2,int r2)//根据前序遍历和后序遍历构造二叉树 
{
	if(r1-l1!=r2-l2||l1>r1) return false;
	bool flag=true;
	if(l1+1<=r1&&a[l1+1]!=c[r2-1])//左右子树均存在 
	{
		flag&=build_13(l1+1,mp1[c[r2-1]]-1,l2,mp2[a[l1+1]]);
		flag&=build_13(mp1[c[r2-1]],r1,mp2[a[l1+1]]+1,r2-1);
	}
	else if(l1!=r1)
	{
		flag=false;
		//假设只有右子树存在
		flag|=build_13(l1+1,r1,l2,r2-1);
		//假设只有左子树存在
		flag|=build_13(l1+1,r1,l2,r2-1);
	}
	else//左右子树均不存在 
		flag=true;
	return flag;
}
//int l[N],r[N];
int idx;
void build_12(int l1,int r1,int l2,int r2)//根据前序遍历和中序遍历求解后序遍历 
{
	if(r1-l1!=r2-l2||r1<l1) return ;
	if(mp[a[l1]]<l2||mp[a[l1]]>r2) return ;
	build_12(l1+1,l1+1+(mp[a[l1]]-1-l2),l2,mp[a[l1]]-1);
	build_12(r1-(r2-mp[a[l1]]-1),r1,mp[a[l1]]+1,r2);
	ans[++idx]=a[l1];
	return ;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),alls.push_back(a[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&c[i]);
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	bool flag=true;//记录根据前序遍历和后序遍历能否建立一棵二叉树 
	for(int i=1;i<=n;i++)
	{
		a[i]=find(a[i]);
		int t=find(c[i]);
		if(c[i]!=alls[t-1])//有可能后序遍历中的某些元素未出现在前序遍历中 
			flag=false;
		c[i]=t;
		mp1[a[i]]=i;
		mp2[c[i]]=i;
	}
	if(flag) flag&=build_13(1,n,1,n);//根据前序遍历和后序遍历尝试构造树 
	if(!flag) puts("Are you kidding me?");
	int k;
	cin>>k;
	while(k--) 
	{
		bool flagg=true;
		map<int,int> mpp;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&b[i]);//b数组中可能存在重复元素 
			if(b[i]!=alls[find(b[i])-1]||mpp[b[i]]) flagg=false;
			mpp[b[i]]=1;
			b[i]=find(b[i]);
			mp[b[i]]=i;
			ans[i]=0;
		}
		idx=0;
		if(!flag)//前序遍历和后序遍历无法确定二叉树 
		{
			if(flagg)
				build_12(1,n,1,n);//利用前序遍历和中序遍历确定二叉树 
			if(idx!=n||!flagg) puts("You must be kidding me!");
			else
				for(int i=1;i<=n;i++)
				{
					printf("%d",alls[ans[i]-1]);
					if(i!=n) printf(" ");
					else puts("");
				}
		}
		else
		{
			if(flagg)
				build_12(1,n,1,n);//用前序遍历和中序遍历重新得到后序遍历序列 
			for(int i=1;i<=n;i++)
				if(ans[i]!=c[i])//如果后续遍历序列不一样,那么就说明前中后序序列不对应 
				{
					flagg=false;
					break; 
				}
			if(flagg) puts("Yes");
			else puts("No"); 
		}
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值