1. 程式人生 > >[LeetCode] Verify Preorder Serialization of a Binary Tree 驗證二叉樹的先序序列化

[LeetCode] Verify Preorder Serialization of a Binary Tree 驗證二叉樹的先序序列化

One way to serialize a binary tree is to use pre-oder traversal. When we encounter a non-null node, we record the node's value. If it is a null node, we record using a sentinel value such as #.

     _9_
    /   \
   3     2
  / \   / \
 4   1  #  6
/ \ / \   / \
# # # #   # #

For example, the above binary tree can be serialized to the string "9,3,4,#,#,1,#,#,2,#,6,#,#"

, where # represents a null node.

Given a string of comma separated values, verify whether it is a correct preorder traversal serialization of a binary tree. Find an algorithm without reconstructing the tree.

Each comma separated value in the string must be either an integer or a character '#' representing null

 pointer.

You may assume that the input format is always valid, for example it could never contain two consecutive commas such as "1,,3".

Example 1:
"9,3,4,#,#,1,#,#,2,#,6,#,#"
Return true

Example 2:
"1,#"
Return false

Example 3:
"9,#,#,1"
Return false

Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.

這道題給了我們一個類似序列化二叉樹後的字串,關於二叉樹的序列化和去序列化可以參見我之前的部落格Serialize and Deserialize Binary Tree,這道題讓我們判斷給定是字串是不是一個正確的序列化的二叉樹的字串。那麼根據之前那邊部落格的解法,我們還是要用istringsteam來操作字串,C++裡面沒有像Java那樣有字串的split函式,可以直接分隔任意字串,我們只能使用getline這個函式,來將字串流的內容都存到一個vector陣列中。我們通過舉一些正確的例子,比如"9,3,4,#,#,1,#,#,2,#,6,#,#" 或者"9,3,4,#,#,1,#,#,2,#,6,#,#"等等,可以觀察出如下兩個規律:

1. 數字的個數總是比#號少一個

2. 最後一個一定是#號

那麼我們加入先不考慮最後一個#號,那麼此時數字和#號的個數應該相同,如果我們初始化一個為0的計數器,遇到數字,計數器加1,遇到#號,計數器減1,那麼到最後計數器應該還是0。下面我們再來看兩個返回False的例子,"#,7,6,9,#,#,#"和"7,2,#,2,#,#,#,6,#",那麼通過這兩個反例我們可以看出,如果根節點為空的話,後面不能再有節點,而且不能有三個連續的#號出現。所以我們再加減計數器的時候,如果遇到#號,且此時計數器已經為0了,再減就成負數了,就直接返回False了,因為正確的序列裡,任何一個位置i,在[0, i]範圍內的#號數都不大於數字的個數的。當迴圈完成後,我們檢測計數器是否為0的同時還要看看最後一個字元是不是#號。參見程式碼如下:

解法一:

class Solution {
public:
    bool isValidSerialization(string preorder) {
        istringstream in(preorder);
        vector<string> v;
        string t = "";
        int cnt = 0;
        while (getline(in, t, ',')) v.push_back(t);
        for (int i = 0; i < v.size() - 1; ++i) {
            if (v[i] == "#") {
                if (cnt == 0) return false;
                --cnt;
            } else ++cnt;
        }
        return cnt == 0 && v.back() == "#";
    }
};

下面這種解法由網友提供,不需要建立解法一中的額外陣列,而是邊解析邊判斷,遇到不合題意的情況直接返回false,而不用全部解析完再來驗證是否合法,提高了運算的效率。我們用一個變數degree表示能容忍的"#"的個數,degree初始化為1。再用一個布林型變數degree_is_zero來記錄degree此時是否為0的狀態,這樣的設計很巧妙,可以cover到"#"開頭,但後面還跟有數字的情況,比如"#,1,2"這種情況,當檢測到"#"時,degree自減1,此時若degree為0了,degree_is_zero賦值為true,那麼如果後面還跟有其他東西的話,在下次迴圈開始開始前,先判斷degree_is_zero,如果為true的話,直接返回false。而當首字元為數字的話,degree自增1,那麼此時degree就成了2,表示後面可以再容忍兩個"#"。當迴圈退出的時候,此時判斷degree是否為0,因為我們要補齊"#"的個數,少了也是不對的,參見程式碼如下:

解法二:

class Solution {
public:
    bool isValidSerialization(string preorder) {
        istringstream in(preorder);
        string t = "";
        int degree = 1;
        bool degree_is_zero = false;;
        while (getline(in, t, ',')) {
            if (degree_is_zero) return false;
            if (t == "#") {
                if (--degree == 0) degree_is_zero = true;
            } else ++degree;
        }
        return degree == 0;
    }
};

下面這種解法就更加巧妙了,連字串解析都不需要了,用一個變數capacity來記錄能容忍"#"的個數,跟上面解法中的degree一個作用,然後我們給preorder末尾加一個逗號,這樣可以處理末尾的"#"。我們遍歷preorder字串,如果遇到了非逗號的字元,直接跳過,否則的話capacity自減1,如果此時capacity小於0了,直接返回true。此時再判斷逗號前面的字元是否為"#",如果不是的話,capacity自增2。這種設計非常巧妙,如果逗號前面是"#",我們capacity自減1沒問題,因為容忍了一個"#";如果前面是數字,那麼先自減的1,可以看作是初始化的1被減了,然後再自增2,因為每多一個數字,可以多容忍兩個"#",最後還是要判斷capacity是否為0,跟上面的解法一樣,我們要補齊"#"的個數,少了也是不對的,參見程式碼如下:

解法三:

class Solution {
public:
    bool isValidSerialization(string preorder) {
        int capacity = 1;
        preorder += ",";
        for (int i = 0; i < preorder.size(); ++i) {
            if (preorder[i] != ',') continue;
            if (--capacity < 0) return false;
            if (preorder[i - 1] != '#') capacity += 2;
        }
        return capacity == 0;
    }
};

類似題目:

參考資料: