114. 二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
将其展开为:
思路一:
先来一个前序遍历把所有结点存在一个列表中,然后遍历链表,把所有结点用右指针串起来
1 /** 2 * Definition for a binary tree node. 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right; 7 * TreeNode() {} 8 * TreeNode(int val) { this.val = val; } 9 * TreeNode(int val, TreeNode left, TreeNode right) { 10 * this.val = val; 11 * this.left = left; 12 * this.right = right; 13 * } 14 * } 15 */ 16 class Solution { 17 public void flatten(TreeNode root) { 18 if(root == null) 19 return ; 20 List<TreeNode> list = new ArrayList<>(); 21 TreeNode top = root; 22 Stack<TreeNode> stack = new Stack<>(); 23 // 先来一个前序遍历把所有结点存在一个列表中 24 while(!stack.empty() || top != null){ 25 while(top != null){ 26 list.add(top); 27 stack.push(top); 28 top = top.left; 29 } 30 top = stack.pop(); 31 top = top.right; 32 } 33 34 // 遍历链表,把所有结点用右指针串起来 35 int n = list.size(); 36 for(int i = 0; i < n; i++){ 37 if(i > 0){ 38 list.get(i - 1).right = list.get(i); 39 } 40 list.get(i).left = null; 41 } 42 } 43 }
力扣测试:时间为2ms, 空间为39MB
复杂度分析:
时间复杂度:一次存,一次遍历,相当于遍历了两次二叉树,所以时间复杂度为O(n), n为节点个数
空间复杂度:列表的大小,所以为O(n)
思路二:
1. 把当前结点的右子树挂到左子树最右边的结点上
2. 把左子树挂到右子树上、
3. 更新当前结点的右子树为新的当前结点,重复上面的操作,直到当前结点为空
1 class Solution { 2 public void flatten(TreeNode root) { 3 TreeNode cur = root; 4 while(cur != null){ 5 // 如果左子树为空,直接进入下个结点 6 if(cur.left == null){ 7 cur = cur.right; 8 }else{ 9 // 如果左子树不为空,找到它最右边的结点 10 TreeNode leftRight = cur.left; 11 while(leftRight.right != null){ 12 leftRight = leftRight.right; 13 } 14 // 将右子树挂到这个结点上 15 leftRight.right = cur.right; 16 17 // 将左子树挂到右子树的位置 18 cur.right = cur.left; 19 cur.left = null; 20 21 // 更新cur指针 22 cur = cur.right; 23 } 24 } 25 26 } 27 }
力扣测试时间为:1ms, 空间为39.2mb
复杂度分析:
这个时间复杂度不太好判断,我觉得应该是O(nlogn),首先总体对每个结点必须访问一次,所以是O(n), 其次内层循环中每次都必须找到左子树最右边的结点,我觉得这个复杂度应该是O(logn),所以我觉的时间复杂度是O(nlogn)
空间复杂度为O(1)
思路三:巧用后序遍历(速度最快)
1. 右、左、根的后序遍历,遍历结果为6->5->4->3->2->1
2. 使用p->right = pre, 即可把逆序编程正序,变为把 1->2->3->4->5->6,pre结点刚好是p结点的左结点,所以不用担心左结点会丢失,
1 class Solution { 2 public void flatten(TreeNode root) { 3 // 右、左、根的后序遍历,遍历结果为6->5->4->3->2->1 4 // 使用p->right = pre, 即可把逆序编程正序,变为把 1->2->3->4->5->6,pre结点刚好是p结点的左结点,所以不用担心左结点会丢失, 5 postTraversal(root); 6 } 7 public TreeNode pre = null; 8 public void postTraversal(TreeNode p){ 9 if(p != null){ 10 postTraversal(p.right); 11 postTraversal(p.left); 12 p.right = pre; 13 p.left = null; 14 pre = p; 15 } 16 } 17 }
力扣测试时间为0ms, 空间为39.8MB
复杂度分析:
时间复杂度为:仅仅是一次后序遍历,所以复杂度为O(n)
空间复杂度:方法栈最大递归层数为树的高度,所以空间复杂度为O(n)
思路四:利用栈
借助一个栈,每次先把右子树压入栈,然后把左子树压入栈,这样每次左子树都会先弹栈,直接把根的右子树指向这个弹出的左子树,因为右子树已经被压入栈中了,所以右子树不会丢失
1 class Solution { 2 public void flatten(TreeNode root) { 3 Stack<TreeNode> stack = new Stack<TreeNode>(); 4 if(root != null){ 5 stack.push(root); 6 } 7 TreeNode pre = null; 8 while(!stack.empty()){ 9 TreeNode top = stack.pop(); 10 if(top.right != null){ 11 stack.push(top.right); 12 } 13 if(top.left != null){ 14 stack.push(top.left); 15 } 16 // 直接把根的右子树指向这个弹出的左子树 17 if(pre != null){ 18 pre.right = top; 19 pre.left = null; 20 } 21 pre = top; 22 } 23 } 24 }
力扣测试时间为2ms, 空间为39.7MB
复杂度分析:
时间复杂度:对每个结点都出栈一次,所以时间复杂度为O(n)
空间复杂度:栈的大小即为空间的花费,这里栈同时存储的是同一层的结点,所以最坏情况下,当树只有两层时,空间复杂度为O(n/2),所以空间复杂度为O(n)