题目
我们从二叉树的根节点 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;
}
}