題目
我們從二叉樹的根節點 root
開始進行深度優先搜索。
在遍歷中的每個節點處,我們輸出 D
條短劃線(其中 D
是該節點的深度),然後輸出該節點的值。(如果節點的深度爲 D
,則其直接子節點的深度爲 D + 1
。根節點的深度爲 0
)。
如果節點只有一個子節點,那麼保證該子節點爲左子節點。
給出遍歷輸出 S
,還原樹並返回其根節點 root
。
示例 1:
輸入:"1-2--3--4-5--6--7"
輸出:[1,2,5,3,4,6,7]
示例 2:
輸入:"1-2--3---4-5--6---7"
輸出:[1,2,5,3,null,6,null,4,null,7]
示例 3:
輸入:"1-401--349---90--88"
輸出:[1,401,null,349,88,90]
提示:
- 原始樹中的節點數介於
1
和1000
之間。 - 每個節點的值介於
1
和10 ^ 9
之間。
解題思路
記當前節點爲 T,上一個節點爲 S,那麼實際上只有兩種情況:
1)T 是 S 的左子節點;
2)T 是根節點到 S 這一條路徑上(不包括 S)某一個節點的右子節點。
爲什麼不包括 S?因爲題目中規定了如果節點只有一個子節點,那麼保證該子節點爲左子節點。在 T 出現之前,S 仍然還是一個葉節點,沒有左子節點,因此 T 如果是 S 的子節點,一定是優先變成 S 的左子節點。
對於在先序遍歷中任意的兩個相鄰的節點 S 和 T,要麼 T 是 S 的左子節點(對應上面的第一種情況),要麼在遍歷到 S 之後發現 S 是個葉節點,於是回溯到之前的某個節點,並開始遞歸地遍歷其右子節點(對應上面的第二種情況)。
當得到當前節點的值以及深度信息之後,可以發現:如果 T 是 S 的左子節點,那麼 T 的深度恰好比 S 的深度大 1;在其它的情況下,T 是棧中某個節點(不包括 S)的右子節點,那麼我們將棧頂的節點不斷地出棧,直到 T 的深度恰好比棧頂節點的深度大 1,此時我們就找到了 T 的父節點。
複雜度分析:
時間複雜度:O(|s|),其中 |s| 是字符串 S 的長度。我們的算法不斷地從 S 中取出一個節點的信息,直到取完爲止。在這個過程中,我們實際上是對 S 進行了一次遍歷。
時間複雜度:O(h),其中 h 是還原出的二叉樹的高度。除了作爲答案返回的二叉樹使用的空間以外,我們使用了一個棧幫助我們進行迭代。由於棧中存放了從根節點到當前節點這一路徑上的所有節點,因此最多會有 h 個節點。
代碼
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode recoverFromPreorder(String S) {
Deque<TreeNode> stack = new LinkedList<>();
int i = 0;
while(i<S.length()){
int level = 0;
// 如果遇見的是字符 ‘-’,繼續向後遍歷
while(S.charAt(i) == '-'){
i++; // 字符索引
level++; // 節點深度
}
int value = 0;
// 如果是數字
while(i<S.length() && Character.isDigit(S.charAt(i))){
value = value*10 + S.charAt(i) - '0';
i++;
}
TreeNode cur = new TreeNode(value);
// 如果當前深度等於棧的深度且棧非空,表示當前節點是棧頂節點的左孩子
if(level == stack.size()){
// 根節點沒有父節點,要進行一次非空的判斷
if(!stack.isEmpty()){
TreeNode node = stack.peek();
node.left = cur;
}
// TreeNode node = stack.peek();
// node.left = cur;
}else{
while(level != stack.size()){
stack.pop();
}
TreeNode node = stack.peek();
node.right = cur;
}
stack.push(cur);
}
while(stack.size()>1){
stack.pop();
}
TreeNode root = stack.peek();
return root;
}
}