1. 程式人生 > >PAT A 1151(甲級 2018年9月真題第四題)

PAT A 1151(甲級 2018年9月真題第四題)

1151 LCA in a Binary Tree(30 分)

作者: CHEN, Yue

單位: 浙江大學

時間限制: 1000 ms

記憶體限制: 64 MB

程式碼長度限制: 16 KB

The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U and V as descendants.

Given any two nodes in a binary tree, you are supposed to find their LCA.

Input Specification:

Each input file contains one test case. For each case, the first line gives two positive integers: M (≤ 1,000), the number of pairs of nodes to be tested; and N (≤ 10,000), the number of keys in the binary tree, respectively. In each of the following two lines, N distinct integers are given as the inorder and preorder traversal sequences of the binary tree, respectively. It is guaranteed that the binary tree can be uniquely determined by the input sequences. Then M lines follow, each contains a pair of integer keys U and V. All the keys are in the range of int

.

Output Specification:

For each given pair of U and V, print in a line LCA of U and V is A. if the LCA is found and A is the key. But if A is one of U and V, print X is an ancestor of Y. where X is A and Y is the other node. If U or V is not found in the binary tree, print in a line ERROR: U is not found.

 or ERROR: V is not found. or ERROR: U and V are not found..

Sample Input:

6 8
7 2 3 4 6 5 1 8
5 3 7 2 6 4 8 1
2 6
8 1
7 9
12 -3
0 8
99 99

Sample Output:

LCA of 2 and 6 is 3.
8 is an ancestor of 1.
ERROR: 9 is not found.
ERROR: 12 and -3 are not found.
ERROR: 0 is not found.
ERROR: 99 and 99 are not found.

今年9月份我參加了PAT甲級考試,也就是在前天,這道題在我看來非常有希望全部的case都AC掉,但是很遺憾,沒有拿滿這30分。

在 PAT個人心得 這篇文章中我也著重的提到了這道題目,這道題連帶第二三題都屬於比較簡單的,但是考試這種東西就是這樣,不僅是你會不會做的問題,這是一場限時的競賽,不僅僅是你能不能寫出來的問題,還有你有沒有時間寫的問題。

這道題開始我的想法就是構建一個樹之後再讓他轉換為靜態樹(雖然後來我也思考到可以直接構建靜態樹,不用構建完連結串列樹之後再去轉換靜態樹,但是因為我當時沒有試過直接通過前中序遍歷構建靜態樹,所以沒有大膽嘗試,走了一個感覺穩一點的路線),這時只要獲取到兩個數字在靜態樹中的下標值,然後讓下標較大的一方不斷除以2 ,直到兩者的下標相等,就可以找到他們的LCA,即使其中一個節點是另一個節點的祖先的這種情況也是適用的。

如果N的範圍特別小,在30及30以內的時候,就可以安心使用靜態樹了,為什麼這麼說呢?這正是我一開始沒有考慮到的點,就是如果是靜態樹的話,而給出的樹偏偏又是所有的節點只有左子樹的那種極端情況,那麼第一個節點的下標是2^{0},第二個節點的下標是2^{1},第N個節點的下標就是2^{N - 1},當N大於31的時候就是無法承受的了,並且N的取值範圍是 <= 10000,這更加無法使用靜態樹。

所以唯一的方法就是使用連結串列樹了,其實連結串列樹也不難,使用DFS的方法去找到目標值,如果找到目標值函式返回目標值,如果沒有找到目標值,返回-1,然後當兩個目標值第一次匯合的時候就是他們的LCA了。這樣其實就是在DFS回溯的過程中,不斷的比較根節點和左右子樹返回的值是不是包含兩個目標值,如果包含就輸出,不包含就接著將目標值返回。

但是也要注意一種特殊情況:目標值相等的時候,這時候是找不到第二個目標值的,也就是當第一次找到目標值的時候就將其輸出就可以了,並且以ancestor的那種形式輸出,具體的細節大家可以從程式碼中去感受。

如果你有更好的程式碼,也可以貼出來和我交流。

AC程式碼:

#include <cstdio>

const int maxn = 10010;

struct Tree {
    int key;
    Tree * lchild, * rchild;
};

int n, m, a, b;
//   pre儲存先序遍歷 in儲存中序遍歷
int pre[maxn], in[maxn];

Tree* createTree(int, int, int, int);
int dfs(Tree *& root);

int main() {
    // 獲取 m n值
    scanf("%d%d", &m, &n);
    for(int i = 0; i < n; ++i) {
        scanf("%d", in + i);
    }
    for(int i = 0; i < n; ++i) {
        scanf("%d", pre + i);
    }

    //  構建樹
    Tree * root = createTree(0, n - 1, 0, n - 1);

    for(int i = 0; i < m; ++i) {
        scanf("%d%d", &a, &b);

        bool fab[2] = {false, false};
        int nfnum = 2;
        for(int j = 0; j < n; ++j) {
            if(a == pre[j]) {
                fab[0] = true;
                --nfnum;
            }
            if(b == pre[j]) {
                fab[1] = true;
                --nfnum;
            }
        }


        if(nfnum > 0) {
            printf("ERROR:", nfnum);
            for(int j = 0; j < 2; ++j) {
                if(!fab[j]) {
                    if(nfnum == 2 && j == 1) {
                        printf("and");
                    }
                    printf(" %d ", j == 0? a : b);
                }
            }
            if(nfnum == 2) {
                printf("are");
            } else {
                printf("is");
            }

            printf(" not found.\n");
        } else {
            dfs(root);
        }
    }

    return 0;
}

//  pl代表先序遍歷的左邊界,pr代表先序遍歷的右邊界,
//  il和ir同理只不過是中序遍歷的兩個邊界
Tree* createTree(int pl, int pr, int il, int ir) {
    if(pl > pr) {
        return NULL;
    }

    Tree * t = new Tree;
    t->key = pre[pl];

    int i;
    for(i = il; i <= ir; ++i) {
        if(in[i] == pre[pl]) {
            break;
        }
    }

    int len = i - il;
    t->lchild = createTree(pl + 1, pl + len, il, i - 1);
    t->rchild = createTree(pl + len + 1, pr, i + 1, ir);

    return t;
}

int dfs(Tree *& root) {
    //  如果root為NULL則返回-1
    if(root == NULL) {
        return -1;
    }

    //  findakey 標誌當前節點的值是不是目標值
    bool findakey = false;
    if(root->key == a || root->key == b) {
        findakey = true;
    }

    //  從左右子節點中尋找目標值
    int lkey = dfs(root->lchild);
    int rkey = dfs(root->rchild);

    //  判斷是不是左右子節點包含兩個目標值
    if(lkey != -1 && rkey != -1) {
        printf("LCA of %d and %d is %d.\n", a, b, root->key);
        return -1;
    } else if(findakey && (lkey != -1 || rkey != -1 || a == b)) {
        //  判斷是不是當前節點的值和其左右子節點中的一個包含目標值,也是就ancestor的情況
        //  並且還需要特別判斷兩個目標值是否相等,如果相等也要輸出
        printf("%d is an ancestor of %d.\n", root->key, a == b ? a : lkey != -1 ? lkey : rkey);
        return -1;
    } else if(findakey) {
        //  如果只有根節點包含目標值 則返回目標值
        return root->key;
    } else if(lkey != -1 || rkey != -1) {
        //  如果左右子樹中只有其一包含目標值,則返回目標值
        return lkey != -1 ? lkey : rkey;
    }

    //  其他情況返回未找到標誌 -1
    return -1;
}

如有錯誤,歡迎指摘。