🌸题目
🍁给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为
1 | 1 |
分析
本题需要将二叉树转化为列表,对于二叉树的题目,无非就以下几种解题思路:
- 先序遍历(深度优先搜索)
- 中序遍历(深度优先搜索)(尤其二叉搜索树)
- 后序遍历(深度优先搜索)
- 层序遍历(广度优先搜索)(尤其按照层来解决问题的时候)
- 序列化与反序列化(结构唯一性问题)
解法一:暴力(常规思路)
可以发现展开的顺序其实就是二叉树的先序遍历,我们需要两步完成这道题。
- 将左子树插入到右子树的地方
- 将原来的右子树接到左子树的最右边节点
- 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
- 可以看图理解下这个过程。
1 | 1 |
1 | public void flatten(TreeNode root) { |
解法二: 深度递归搜索
其实是分为三步:
- 首先将根节点的左子树变成链表
- 其次将根节点的右子树变成链表
- 最后将变成链表的右子树放在变成链表的左子树的最右边
这就是一个递归的过程,递归的一个非常重要的点就是:不去管函数的内部细节是如何处理的,我们只看其函数作用以及输入与输出。对于函数flatten来说:
- 函数作用:将一个二叉树,原地将它展开为链表
- 输入:树的根节点
- 输出:无
1 | public void flatten(TreeNode root) { |
解法三:先序遍历递归
其实就是将二叉树通过右指针,组成一个链表。
1 | 1 -> 2 -> 3 -> 4 -> 5 -> 6 |
我们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们能不能利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。
先序遍历的顺序是 1 2 3 4 5 6。
遍历到 2,把 1 的右指针指向 2。1 -> 2 3 4 5 6。
遍历到 3,把 2 的右指针指向 3。1 -> 2 -> 3 4 5 6。
… …
一直进行下去似乎就解决了这个问题。但现实是残酷的,原因就是我们把 1 的右指针指向 2,那么 1 的原本的右孩子就丢失了,也就是 5 就找不到了。
解决方法的话,我们可以逆过来进行。
我们依次遍历 6 5 4 3 2 1,然后每遍历一个节点就将当前节点的右指针更新为上一个节点。
遍历到 5,把 5 的右指针指向 6。6 <- 5 4 3 2 1。
遍历到 4,把 4 的右指针指向 5。6 <- 5 <- 4 3 2 1。
… …
1 | 1 |
这样就不会有丢失孩子的问题了,因为更新当前的右指针的时候,当前节点的右孩子已经访问过了。
而 6 5 4 3 2 1 的遍历顺序其实变形的后序遍历,遍历顺序是右子树->左子树->根节点。
先回想一下后序遍历的代码
1 | public void PrintBinaryTreeBacRecur(TreeNode<T> root){ |
这里的话,我们不再是打印根节点,而是利用一个全局变量 pre
,更新当前根节点的右指针为 pre
,左指针为 null
。
1 | private TreeNode pre = null; |
相应的左孩子也要置为 null,同样的也不用担心左孩子丢失,因为是后序遍历,左孩子已经遍历过了。和 112 题一样,都巧妙的利用了后序遍历。
既然后序遍历这么有用,利用 112 题介绍的后序遍历的迭代方法,把这道题也改一下吧。
1 | public void flatten(TreeNode root) { |
解法四: 先序遍历迭代
解法二中提到如果用先序遍历的话,会丢失掉右孩子,除了用后序遍历,还有没有其他的方法避免这个问题。在 Discuss 又发现了一种解法。
为了更好的控制算法,所以我们用先序遍历迭代的形式,正常的先序遍历代码
1 | public static void preOrderStack(TreeNode root) { |
还有一种特殊的先序遍历,提前将右孩子保存到栈中,我们利用这种遍历方式就可以防止右孩子的丢失了。由于栈是先进后出,所以我们先将右节点入栈。
1 | public static void preOrderStack(TreeNode root) { |
之前我们的思路如下:
题目其实就是将二叉树通过右指针,组成一个链表。
1 | 1 -> 2 -> 3 -> 4 -> 5 -> 6 |
我们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们可以利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。
先序遍历的顺序是 1 2 3 4 5 6。
遍历到 2,把 1 的右指针指向 2。1 -> 2 3 4 5 6。
遍历到 3,把 2 的右指针指向 3。1 -> 2 -> 3 4 5 6。
… …
因为我们用栈保存了右孩子,所以不需要担心右孩子丢失了。用一个 pre 变量保存上次遍历的节点。修改的代码如下:
1 | public void flatten(TreeNode root) { |
题解来自@windiang