二叉树展开为链表

2020-06-22 17:38 更新

题目

给定一个二叉树,原地将它展开为一个单链表。

例如,给定二叉树

  1. 1
  2. / \
  3. 2 5
  4. / \ \
  5. 3 4 6

将其展开为:

  1. 1
  2. \
  3. 2
  4. \
  5. 3
  6. \
  7. 4
  8. \
  9. 5
  10. \
  11. 6

题解一

解法一 可以发现展开的顺序其实就是二叉树的先序遍历。算法和 94 题中序遍历的 Morris 算法有些神似,我们需要两步完成这道题。

  1. 将左子树插入到右子树的地方
  2. 将原来的右子树接到左子树的最右边节点
  3. 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null

  1. public void flatten(TreeNode root) {
  2. while (root != null) {
  3. //左子树为 null,直接考虑下一个节点
  4. if (root.left == null) {
  5. root = root.right;
  6. } else {
  7. // 找左子树最右边的节点
  8. TreeNode pre = root.left;
  9. while (pre.right != null) {
  10. pre = pre.right;
  11. }
  12. //将原来的右子树接到左子树的最右边节点
  13. pre.right = root.right;
  14. // 将左子树插入到右子树的地方
  15. root.right = root.left;
  16. root.left = null;
  17. // 考虑下一个节点
  18. root = root.right;
  19. }
  20. }
  21. }

解法二

如果用先序遍历的话,会丢失掉右孩子,除了用后序遍历,还有没有其他的方法避免这个问题。在 Discuss 又发现了一种解法。 为了更好的控制算法,所以我们用先序遍历迭代的形式,正常的先序遍历代码如下,

  1. public static void preOrderStack(TreeNode root) {
  2. if (root == null) {
  3. return;
  4. }
  5. Stack<TreeNode> s = new Stack<TreeNode>();
  6. while (root != null || !s.isEmpty()) {
  7. while (root != null) {
  8. System.out.println(root.val);
  9. s.push(root);
  10. root = root.left;
  11. }
  12. root = s.pop();
  13. root = root.right;
  14. }
  15. }
  16. ##解法三
  17. 看题意顺序明显是先序遍历,二叉树的深度遍历本来只用递,没有用到归,所以我判定把归利用起来,肯定是能够解题的。
  18. 一句话思路就是先序遍历,如果走到最左边,则把此节点指针返回到有右节点的位置,把该右节点挪到最左边节点的左边,继续递归即可。
  19. 需要注意的是,一定要注意将右节点接在左叶子节点之后记得把右节点清空。
  20. 还有如果将nullLeftNode左叶子节点指针改为全局变量会省事儿很多,不用归,直接递,但是考虑到原地展开,就没有用到外部存储空间。
  21. 本解法时间复杂度O(n),因为每一个节点都遍历了一遍;空间复杂度O(1),只有两个临时节点。

还有一种特殊的先序遍历,提前将右孩子保存到栈中,我们利用这种遍历方式就可以防止右孩子的丢失了。由于栈是先进后出,所以我们先将右节点入栈。

  1. public static void preOrderStack(TreeNode root) {
  2. if (root == null){
  3. return;
  4. }
  5. Stack<TreeNode> s = new Stack<TreeNode>();
  6. s.push(root);
  7. while (!s.isEmpty()) {
  8. TreeNode temp = s.pop();
  9. System.out.println(temp.val);
  10. if (temp.right != null){
  11. s.push(temp.right);
  12. }
  13. if (temp.left != null){
  14. s.push(temp.left);
  15. }
  16. }
  17. }

题解三

先序遍历,二叉树的深度遍历本来只用递,没有用到归,所以我判定把归利用起来,肯定是能够解题的。 一句话思路就是先序遍历,如果走到最左边,则把此节点指针返回到有右节点的位置,把该右节点挪到最左边节点的左边,继续递归即可。 需要注意的是,一定要注意将右节点接在左叶子节点之后记得把右节点清空。 还有如果将nullLeftNode左叶子节点指针改为全局变量会省事儿很多,不用归,直接递,但是考虑到原地展开,就没有用到外部存储空间。 本解法时间复杂度O(n),因为每一个节点都遍历了一遍;空间复杂度O(1),只有两个临时节点。

  1. class Solution {
  2. public void flatten(TreeNode root) {
  3. if(root==null){
  4. return ;
  5. }
  6. //先将二叉树转换成左展开链表
  7. flattenHelper(root);
  8. //再将左展开转换成右展开链表
  9. TreeNode tempNode =root;
  10. while(tempNode.left!=null){
  11. tempNode.right = tempNode.left;
  12. tempNode.left = null;
  13. tempNode= tempNode.right;
  14. }
  15. }
  16. private TreeNode flattenHelper(TreeNode root){
  17. //如果左右节点都为空则返回当前节点,也就是最后的左边的节点;
  18. if(root.left==null&&root.right==null){
  19. return root;
  20. }
  21. //如果左为空右不为空,则把右边的移到左边,然后继续递归左边的;
  22. if(root.left==null&&root.right!=null){
  23. root.left=root.right;
  24. root.right=null;
  25. return flattenHelper(root.left);
  26. }
  27. //如果左不为空右为空则直接递归左边的;
  28. if(root.left!=null&&root.right==null){
  29. return flattenHelper(root.left);
  30. }
  31. //如果左右都不为空则把左边归过来的末节点的左节点赋值为当前节点的右节点,然后继续递归;
  32. TreeNode nullLeftNode= flattenHelper(root.left);
  33. nullLeftNode.left = root.right;
  34. root.right=null;
  35. return flattenHelper(nullLeftNode.left);
  36. }
  37. }
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号