原题
给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
- 来源:力扣(LeetCode)
- 难度:中等
- 链接:leetcode-cn.com/problems/fl…
解法
因在下水平有限,探索出两种方法!
方式一:使用类似Morris方法,找最右节点
展开后的顺序是先序遍历。使用类似Morris方法,找左子树的最右节点,然后将左子树直接接在最右节点后,然后考虑下一个结点,依次以上操作!
- 将左子树插入到右子树的地方
- 将原来的右子树接到左子树的最右边节点
- 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
如图理解:
代码如下:
public void flatten(TreeNode root) {
//判空
if(root==null){
return;
}
while (root!=null){
//如果左子树为空,直接考虑下一个节点,即直接转到右子树
if(root.left==null) {
root = root.right;
}else{
TreeNode pre = root.left;//找左子树的最右结点
while (pre.right!=null){
pre = pre.right;
}
//将原来的右子树接到左子树的最右边节点
pre.right = root.right;
//将左子树接到原来右子树位置
root.right = root.left;
//左子树置空
root.left = null;
//继续考虑下一个节点
root = root.right;
}
}
}
方式二:特殊的先序遍历
普通的先序遍历(迭代)
public void preorder(TreeNode root){
Deque<TreeNode> stk = new LinkedList<>();
if (root==null){
return;
}
while (root!=null || !stk.isEmpty()){
while (root!=null){
System.out.println(root.val);//根结点
stk.push(root.left);//左节点入栈
}
root = stk.pop();
root = root.right;//访问右节点
}
}
特殊的先序遍历(提前将右孩子保存到栈中,我们利用这种遍历方式就可以防止右孩子的丢失了)
public void preorder2(TreeNode root){
Deque<TreeNode> stk = new LinkedList<>();
if (root==null){
return;
}
stk.push(root);//根结点入栈
while (!stk.isEmpty()){
TreeNode temp = stk.pop();
System.out.println(temp.val);//访问根结点
if (temp.right!=null){//右节点不为空就先入栈,因为栈是先进后出,而先序遍历的右节点最后遍历
stk.push(temp.right);
}
if (temp.left!=null){
stk.push(temp.left);
}
}
}
到这,我们应该有了思路了,
- 题目其实就是将二叉树通过右指针,组成一个链表,
- 就是说,我们用temp代表当前访问的结点,
- 只需要将当前结点的左节点接到当前结点的右节点位置,就可以完成整体往右子树拉伸为链表操作!
- 所以我们可以利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。
- 因为我们用栈保存了右孩子,所以不需要担心右孩子丢失了。用一个 pre 变量保存上次遍历的节点
代码示例:
public void flatten2(TreeNode root) {
Deque<TreeNode> stk = new LinkedList<>();
if (root==null){
return;
}
stk.push(root);//根结点入栈
TreeNode pre =null;
while (!stk.isEmpty()){
TreeNode temp = stk.pop();
// System.out.println(temp.val);//访问根结点
if(pre!=null){//不是第一次遍历,存在上一结点
pre.right = temp;//当前节点其实是上一节点的左节点或者右节点,让上一节点右节点指向当前节点
pre.left = null;//上一节点的左结点置空
}
if (temp.right!=null){//右节点不为空就先入栈,因为栈是先进后出,而先序遍历的右节点最后遍历
stk.push(temp.right);
}
if (temp.left!=null){//左节点入栈
stk.push(temp.left);
}
pre = temp;//用pre保存上一次遍历的结点,以备安排其左右结点
}
}
总体来说,第一种容易理解,第二种难以理解,但其空间复杂度有所降低!