We run a preorder depth first search on the `root`of a binary tree.
At each node in this traversal, we output D
dashes (where D
is the depth of this node), then we output the value of this node. (If the depth of a node is D
, the depth of its immediate child is D+1
. The depth of the root node is 0
.)
If a node has only one child, that child is guaranteed to be the left child.
Given the output S
of this traversal, recover the tree and return its root
.
Example 1:
Input: "1-2--3--4-5--6--7"
Output: [1,2,5,3,4,6,7]
Example 2:
Input: "1-2--3---4-5--6---7"
Output: [1,2,5,3,null,6,null,4,null,7]
Example 3:
Input: "1-401--349---90--88"
Output: [1,401,null,349,88,90]
Note:
- The number of nodes in the original tree is between
1
and1000
. - Each node will have a value between
1
and10^9
.
這道題讓我們根據一棵二叉樹的先序遍歷的結果來重建這棵二叉樹,之前有過根據三種遍歷方式-先序,中序,后序中的兩個來重建二叉樹 [Construct Binary Tree from Inorder and Postorder Traversal](http://www.cnblogs.com/grandyang/p/4296193.html) 和 [Construct Binary Tree from Preorder and Inorder Traversal](http://www.cnblogs.com/grandyang/p/4296500.html),因為一種遍歷方式得到的結點值數組是無法唯一的重建出一棵二叉樹的。這里為了能夠只根據先序遍歷的結果來唯一的重建出二叉樹,提供了每個結點值的深度,用短杠的個數來表示,根結點的深度為0,前方沒有短杠,后面的數字前方只有一個短杠的就是根結點的左右子結點,然后緊跟在一個短杠后面的兩個短杠的數字就是根結點左子結點的左子結點,以此類推。而且題目還說了,若某個結點只有一個子結點,那么一定是左子結點,這就保證了樹結構的唯一性。其實這道題還是蠻有難度的,輸入只給了一個字符串,我們不但要把結點值和深度分別提取出來,還要正確的組成樹的結構。由於先序遍歷的特點,左右子結點的位置可能相隔很遠,就拿例子1來說吧,根結點1的左結點2是緊跟在后面的,但是根結點1的右子結點5卻在很后面,而且有時候也不一定存在右子結點,博主剛開始想的是先查找右子結點的位置,然后調用遞歸,但是發現不好查找,為啥呢,因為 C++ 中好像沒有 whole match 的查找功能,這里需要要查找一個杠,且前后位置都不能是杠,后來覺得若樹的深度很大的話,這種處理方式貌似不是很高效。得換一個角度來想問題,我們在寫非遞歸的先序遍歷的時候,使用了棧來輔助遍歷,這里同樣也可以利用棧的后入先出的特點來做。
遍歷輸入字符串,先提取短杠的個數,因為除了根結點之外,所有的深度值都是在結點值前面的,所有用一個 for 循環先提取出短杠的個數 level,然后提取結點值,也是用一個 for 循環,因為結點值可能是個多位數,有了結點值之后我們就可以新建一個結點了。下一步就比較 tricky 了,因為先序遍歷跟 DFS 搜索一樣有一個回到先前位置的過程,比如例子1中,當我們遍歷到結點5的時候,此時是從葉結點4回到了根結點的右子結點5,現在棧中有4個結點,而當前深度為1的結點5是要連到根結點的,所以棧中的無關結點就要移除,需要把結點 2,3,4 都移除,就用一個 while 循環,假如棧中元素個數大於當前的深度 level,就移除棧頂元素。那么此時棧中就只剩根結點了,就可以連接了。此時我們的連接策略是,假如棧頂元素的左子結點為空,則連在左子結點上,否則連在右子結點上,因為題目中說了,假如只有一個子結點,一定是左子結點。然后再把當前結點壓入棧即可,字符串遍歷結束后,棧中只會留有一個結點(題目中限定了樹不為空),就是根結點,直接返回即可,參見代碼如下:
解法一:
class Solution {
public:
TreeNode* recoverFromPreorder(string S) {
vector<TreeNode*> st;
int i = 0, level = 0, val = 0, n = S.size();
while (i < n) {
for (level = 0; i < n && S[i] == '-'; ++i) {
++level;
}
for (val = 0; i < n && S[i] != '-'; ++i) {
val = 10 * val + (S[i] - '0');
}
TreeNode *node = new TreeNode(val);
while (st.size() > level) st.pop_back();
if (!st.empty()) {
if (!st.back()->left) st.back()->left = node;
else st.back()->right = node;
}
st.push_back(node);
}
return st[0];
}
};
雖然博主最開始想的遞歸方法不太容易實現,但其實這道題也是可以用遞歸來做的,這里我們需要一個全局變量 cur,表示當前遍歷字符串S的位置,遞歸函數還要傳遞個當前的深度 level。在遞歸函數中,首先還是要提取短杠的個數,但是這里有個很 tricky 的地方,我們在統計短杠個數的時候,不能更新 cur,因為 cur 是個全局變量,當統計出來的短杠個數跟當前的深度不相同,就不能再繼續處理了,如果此時更新了 cur,而沒有正確的復原的話,就會出錯。博主成功入坑,檢查了好久才找出原因。當短杠個數跟當前深度相同時,我們繼續提取出結點值,然后新建出結點,對下一層分別調用遞歸函數賦給新建結點的左右子結點,最后返回該新建結點即可,參見代碼如下:
解法二:
class Solution {
public:
TreeNode* recoverFromPreorder(string S) {
int cur = 0;
return helper(S, cur, 0);
}
TreeNode* helper(string& S, int& cur, int level) {
int cnt = 0, n = S.size(), val = 0;
while (cur + cnt < n && S[cur + cnt] == '-') ++cnt;
if (cnt != level) return nullptr;
cur += cnt;
for (; cur < n && S[cur] != '-'; ++cur) {
val = 10 * val + (S[cur] - '0');
}
TreeNode *node = new TreeNode(val);
node->left = helper(S, cur, level + 1);
node->right = helper(S, cur, level + 1);
return node;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1028
類似題目:
Construct Binary Tree from Inorder and Postorder Traversal
Construct Binary Tree from Preorder and Inorder Traversal
參考資料:
https://leetcode.com/problems/recover-a-tree-from-preorder-traversal/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)