PAT-A 1138 题解(两种解法)

PAT-A 1138 题解

题意分析:

给出一个树的先序遍历和中序遍历,要求后序遍历输出的第一个数。

考察要点

对于后续遍历过程的认识

算法分析

这道题和后面所有的树的题基本上类似,只需要求局部解,所以我们不需要构建出整个树。只需要利用先序遍历和中序遍历的性质就能把这一个解求出来。
首先分析一下后续遍历的第一个数是怎么输出的:
从根节点开始,不断地分别往左子树和右子树递归。当到达叶子结点的时候输出。而且这个点一定是唯一的。也就是说,存在一条从根节点通往这个节点的唯一路径。
我们换一种思路。这个路径应该是这样来的:从根节点开始,每一次优先选择左子树进行递归,只有当左子树不存在的时候才换成右子树递归,直到左右子树都无法进行递归的时候,选择输出这个点。这就是第一个点的输出过程。那么代码就好写了。这实际上就是一道模板题。

代码

#include<iostream>
#include<cstdio>
#include<unordered_map>
using namespace std;
const int MAX_N =  50010;
int pre[MAX_N],in[MAX_N];
unordered_map<int,int> pos_mappping;//用于映射value-index的关系
int n;
//解法一,利用后序遍历的性质
void find(int in_l_pos,int in_r_pos,int pre_root_pos){
    //当中序遍历的左右区间重合的时候,说明它已经没有左右子树了
    //这个时候一定是递归的终点。可以放心大胆地输出
    if(in_r_pos == in_l_pos) {
        cout<<in[in_l_pos]<<endl;
        return;
    }
    //中序遍历的根节点的下标索引
    int in_root_pos = pos_mappping[pre[pre_root_pos]];   
    int left_num = in_root_pos - in_l_pos;
    //如果左边还能递归下去,就往左边递归
    if(in_l_pos<in_root_pos){
        find(in_l_pos,in_root_pos-1,pre_root_pos+1);
    }
    //如果发现左边无法递归了,就选择往右边递归。
    else
    {
        find(in_root_pos+1,in_r_pos,pre_root_pos+1+left_num);
    }
    //左边不能递归了,那就往右边递归下去
    //因为这里的递归选择是线性增长的,而不是之前的指数增长,所以时间复杂度为O(log n)
}
int main(){
    cin>>n;
    for(int i = 1;i<=n;i++){
        scanf("%d",&pre[i]);
    }
    for(int i = 1;i<=n;i++){
        scanf("%d",&in[i]);
        pos_mappping[in[i]] = i;
    }
    find(1,n,1);
    system("pause");
    return 0;
}

接下来介绍解法二,也就是直接构建出一颗完整的二叉树。然后对二叉树做后序遍历

#include<iostream>
#include<cstdio>
#include<unordered_map>
using namespace std;
const int MAX_N =  50010;
int pre[MAX_N],in[MAX_N];
unordered_map<int,int> pos_mappping;//用于映射value-index的关系
int n;
struct node
{
    node* lchild;
    node* rchild;
    int data;
};

void find(int in_l_pos,int in_r_pos,int pre_root_pos){
    //当中序遍历的左右区间重合的时候,说明它已经没有左右子树了
    //这个时候一定是递归的终点。可以放心大胆地输出
    if(in_r_pos == in_l_pos) {
        cout<<in[in_l_pos]<<endl;
        return;
    }
    //中序遍历的根节点的下标索引
    int in_root_pos = pos_mappping[pre[pre_root_pos]];   
    int left_num = in_root_pos - in_l_pos;
    //如果左边还能递归下去,就往左边递归
    if(in_l_pos<in_root_pos){
        find(in_l_pos,in_root_pos-1,pre_root_pos+1);
    }
    //如果发现左边无法递归了,就选择往右边递归。
    else
    {
        find(in_root_pos+1,in_r_pos,pre_root_pos+1+left_num);
    }
    //左边不能递归了,那就往右边递归下去
    //因为这里的递归选择是线性增长的,而不是之前的指数增长,所以时间复杂度为O(log n)
}

node* create(int inL,int inR,int preRoot){
    if(inL > inR){
        return NULL;
    }
    node* root = new node;
    root->data = pre[preRoot];
    int inRoot = pos_mappping[pre[preRoot]];
    int left_num = inRoot-inL;
    root->lchild = create(inL,inRoot-1,preRoot+1);
    root->rchild = create(inRoot+1,inR,preRoot+left_num+1);
    return root;
}

bool hasPrint = false;
void postOrder(node* root){
    if(hasPrint){
        return;
    }
    if(root == NULL){
        return;
    }
    postOrder(root->lchild);
    postOrder(root->rchild);
    if(!hasPrint){
        cout<<root->data<<endl;
        hasPrint = true;
    }
}
int main(){
    cin>>n;
    for(int i = 1;i<=n;i++){
        scanf("%d",&pre[i]);
    }
    for(int i = 1;i<=n;i++){
        scanf("%d",&in[i]);
        pos_mappping[in[i]] = i;
    }
    //find(1,n,1);
    node* root = create(1,n,1);
    postOrder(root);
    system("pause");
    return 0;
}


总结

PAT近几年考察树这方面的知识考的比较灵活,只要你对树这个数据结构了解的很透彻,基本上很少要你从零开始直接构建一棵树出来。像这道题只要利用好这个性质,时间复杂度就在O(log n),这是一种很优秀的解法。而后一种建树的解法也可以用,一开始还以为会TLE,但发现好像其实能过。。。也不知道这道题50000的数据规模开了来干嘛的。。。如果有个测试点数据规模到40000以上,这个点掘b过不了,只能说明PAT的测试数据还是有点水。。

上一篇:C#序列化与反序列化方式简单总结


下一篇:java中对List>中的中文汉字排序