One way to serialize a binary tree is to use pre-order 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:
Input:"9,3,4,#,#,1,#,#,2,#,6,#,#"
Output:true
Example 2:
Input:"1,#"
Output:false
Example 3:
Input:"9,#,#,1"
Output: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() == "#"; } };
下面這種解法由網友edyyy提供,不需要建立解法一中的額外數組,而是邊解析邊判斷,遇到不合題意的情況直接返回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; } };
類似題目:
Serialize and Deserialize Binary Tree
參考資料:
https://leetcode.com/problems/verify-preorder-serialization-of-a-binary-tree/
https://discuss.leetcode.com/topic/36035/2-lines-java-using-regex
https://discuss.leetcode.com/topic/45326/c-4ms-solution-o-1-space-o-n-time