一、题目
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
二、递归解法
1. 解题思路
题目其实就是将二叉树通过右指针,组成一个单链表。
1 -> 2 -> 3 -> 4 -> 5 -> 6
思路1:
看到最后的单链表,很容易想到这是二叉树先序遍历的结果,我们先试试通过先序遍历的思路去模拟。
-
先序遍历的访问顺序就是1 2 3 4 5 6,
-
遍历到1,把1的右指针指向2,这时候会导致右孩子5找不到了,通过全局变量记录右孩子,发现当遍历到2的时候,还是会存在同样的问题,所以此方法行不通。
-
那能不能遍历1时先不动,遍历到 2,把 1 的右指针指向 2呢,好像行得通,我们先试试。1 -> 2 3 4 5 6。
-
遍历到 3,把 2 的右指针指向 3。1 -> 2 -> 3 4 5 6。
-
当遍历原有4的节点时,发现此时找不到4了,已经变成3了,同样的,5节点也找不到,此方法行不通,会出现右孩子找不到的情况。
这种右孩子丢失的问题解决呢?通过全局变量记录的方法行不通,那我们可不可以逆向思考一下,我们先遍历访问右孩子节点呢?这就产生了思路2.
思路2:
我们依次遍历 6 5 4 3 2 1,然后每遍历一个节点就将当前节点的右指针更新为上一个访问的节点。
-
首先访问到6,记录pre值
-
遍历到 5,把 5 的右指针指向 6。6 <- 5 4 3 2 1。
-
遍历到 4,把 4 的右指针指向 5。6 <- 5 <- 4 3 2 1。
-
…
这样就不会有丢失孩子的问题了,因为更新当前的右指针的时候,当前节点的右孩子已经访问过了。
2. 编码
由思路2可知,对节点的访问顺序是6 5 4 3 2 1,实际上就是右子树->左子树->根节点,发现和二叉树的后序遍历很像,二叉树的后序遍历顺序是右子树->左子树->根节点,我们只需稍微变通即可。
二叉树后序遍历递归代码模版:
/**
* 后序遍历递归 左子 右子 根
*/
fun postOrderTraverse(root: BTreeNode?) {
if (root == null) {
return
}
postOrderTraverse(root.left)
postOrderTraverse(root.right)
print("${root.value} ")
}
调换左右子树的先后顺序,即可得到此题的递归解法。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private TreeNode pre = null;
public void flatten(TreeNode root) {
if(root == null ){
return ;
}
//左右子树访问顺序相对于后序遍历发生改变
flatten(root.right);
flatten(root.left);
//本题对遍历了的节点处理
root.right = pre;
root.left = null;
pre = root;
}
}
3. 时空复杂度分析
时间复杂度:每个元素都必须访问一次,所以是 O(n)
空间复杂度:最坏的情况下,需要存放 O(h) 个函数调用(h是树的高度),所以是 O(h),h= logN
三、迭代解法
1. 解题思路
还是回归到输出结果,发现就是先序遍历的顺序,所以可以做如下处理:
- 将左子树插入到右子树的地方
- 将原来的右子树接到左子树的最右边节点
- 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
图解:
1 1
/ \
2 5 ---> 2 5
/ \ \ / \ \
3 4 6 3 4 6
//将 1 的左子树插入到右子树的地方
1
\
2 5
/ \ \
3 4 6
//将原来的右子树接到左子树的最右边节点
1
\
2
/ \
3 4
\
5
\
6
//将 2 的左子树插入到右子树的地方
1
\
2
\
3 4
\
5
\
6
//将原来的右子树接到左子树的最右边节点
1
\
2
\
3
\
4
\
5
\
6
......
2. 编码
此题示例代码:
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/
class Solution {
fun flatten(node: TreeNode?): Unit {
var root = node
while(root!=null){
if(root.left == null ){
root = root?.right
}else{
//找到root左子树的最右侧节点
var leftTreeBottomNode = root?.left
while(leftTreeBottomNode.right!=null){
leftTreeBottomNode = leftTreeBottomNode.right
}
//将root右子树拼接在leftTreeBottomNode的right指针
leftTreeBottomNode?.right = root?.right
root?.right = root?.left
root?.left = null
//处理下一个节点
root = root.right
}
}
}
}
3.时空复杂度分析
时间复杂度:每个元素都必须访问一次,寻找左子树的最右节点最差是O(h),h = logN, 所以是时间复杂度是O(NlogN)
空间复杂度:没什么说的,O(1)
四、总结
- 此题难度为中等
- 对于二叉树的二叉树的遍历,有根 左子 右子(前序遍历)、左子 根 右子(中序遍历)、左子 右子 根(后序遍历)、 根 右子 左子、右子 根 右子、 右子 右子 根。
leetcode传送门:114. 二叉树展开为链表