Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树

Posted Yawn,

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树相关的知识,希望对你有一定的参考价值。

1. 二叉树展开为链表

(1)递归

  • 首先将根节点的左子树变成链表
  • 其次将根节点的右子树变成链表
  • 最后将变成链表的右子树放在变成链表的左子树的最右边

模拟一下:

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

//左走到最里面,然后弹出来
      2         
     / \\         
    3   4            
//用temp存储4,将3连在2的右边
     2         
      \\         
        3         temp -> 4
            
 //找到root最右边节点,也就是3,将4连上去,这就得到了2-3-4
     2          
       \\          
         3      
          \\
            4  
        
同样可得到5-6,然后连接起来即可

代码实现:

/**
 * 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 {
    public void flatten(TreeNode root) {
        if(root == null){
            return ;
        }
        //将根节点的左子树变成链表
        flatten(root.left);

        //将根节点的右子树变成链表
        flatten(root.right);

        //把树的右边换成左边的链表
        TreeNode temp = root.right;
        root.right = root.left;

        //记得要将左边置空
        root.left = null;

        //找到left的最右边的节点
        while(root.right != null) 
        	root = root.right;
        
        //把右边的链表接到刚才树的最右边的节点
        root.right = temp;
    }
}


(2)先序遍历(迭代)

可以发现展开的顺序其实就是二叉树的先序遍历。

  • 将左子树插入到右子树的地方
  • 将原来的右子树接到左子树的最右边节点
  • 考虑新的右子树的根节点,一直重复上边的过程,直到新的右子树为 null
    1
   / \\
  2   5
 / \\   \\
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         
  
  ......
public void flatten(TreeNode root) {
    while (root != null) { 
        //左子树为 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;
        }
    }
}

(3)直接改变链表指向(递归)

们知道题目给定的遍历顺序其实就是先序遍历的顺序,所以我们能不能利用先序遍历的代码,每遍历一个节点,就将上一个节点的右指针更新为当前节点。

先序遍历的顺序是 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
   / \\
  2   5
 / \\   \\
3   4   6

这样就不会有丢失孩子的问题了,因为更新当前的右指针的时候,当前节点的右孩子已经访问过了。
而 6 5 4 3 2 1 的遍历顺序其实变形的后序遍历,遍历顺序是右子树->左子树->根节点。

public void flatten(TreeNode root) { 
    Stack<TreeNode> toVisit = new Stack<>();
    TreeNode cur = root;
    TreeNode pre = null;

    while (cur != null || !toVisit.isEmpty()) {
        while (cur != null) {
            toVisit.push(cur); // 添加根节点
            cur = cur.right; // 递归添加右节点
        }
        cur = toVisit.peek(); // 已经访问到最右的节点了
        // 在不存在左节点或者右节点已经访问过的情况下,访问根节点
        if (cur.left == null || cur.left == pre) {
            toVisit.pop(); 
            /**************修改的地方***************/
            cur.right = pre;
            cur.left = null;
            /*************************************/
            pre = cur;
            cur = null;
        } else {
            cur = cur.left; // 左节点还没有访问过就先访问左节点
        }
    } 
}

2. 将有序数组转换为二叉搜索树

(1)递归

  • 根据升序数组,恢复一棵高度平衡的BST。
  • BST的中序遍历是升序的,因此本题等同于根据中序遍历的序列恢复二叉搜索树。因此我们可以以升序序列中的任一个元素作为根节点,以该元素左边的升序序列构建左子树,以该元素右边的升序序列构建右子树,这样得到的树就是一棵二叉搜索树啦
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return dfs(nums, 0, nums.length - 1);
    }

    private TreeNode dfs(int[] nums, int lo, int hi) {
        if (lo > hi) {
            return null;
        } 
        // 以升序数组的中间元素作为根节点 root。
        int mid = lo + (hi - lo) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        // 递归的构建 root 的左子树与右子树。
        root.left = dfs(nums, lo, mid - 1);
        root.right = dfs(nums, mid + 1, hi); 
        return root;
    }
}


3. 有序链表转换二叉搜索树

(1)快慢指针 + 递归

  • 就是找链表中间节点作为根节点,然后再找中点两边的子链表的中点,一直递归下去直到子链表为空
  • 特例处理:如果head为空返回空,如果head.next为空返回值为head.val的树节点
  • 利用快慢指针找链表中间节点(slow每次走一步,fast每次走两步,循环停止时slow指向中间节点)
  • 同时记录一下slow的前一个节点pre,这是为后面的断开操作做准备
  • 创建树的根节点,把slow的值赋给它,并断开链表中间节点和左边子链表的连接
  • 递归链表中间节点左右两边的子链表,找子链表的中间节点,再找子链表的子链表的中间节点,如此循环往复,直到符合特例处理的条件递归返回
class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        //1. 特例处理
        if (head == null){
            return null;
        }else if(head.next == null){
            return new TreeNode(head.val);
        }
        //2. 利用快慢指针找链表中间节点
        ListNode slow = head, fast = head;
        ListNode pre = null;
        while( fast != null && fast.next != null){
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        //3. 创建树的根节点,并断开相应连接
        TreeNode root = new TreeNode(slow.val);
        pre.next = null;

        //4. 递归链表中间节点左右两边的子链表
        root.left = sortedListToBST(head);
        root.right = sortedListToBST(slow.next);
        return root;
    }
}

(2)list

实际上还可以把链表中的值全都存储到集合list中,每次把list分为两部分,和上面原理一样

    public TreeNode sortedListToBST(ListNode head) {
        List<Integer> list = new ArrayList<>();
        //把链表节点值全部提取到list中
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        return sortedListToBSTHelper(list, 0, list.size() - 1);

    }

    TreeNode sortedListToBSTHelper(List<Integer> list, int left, int right) {
        if (left > right)
            return null;
        //把list中数据分为两部分
        int mid = left + (right - left) / 2;
        TreeNode root = new TreeNode(list.get(mid));
        root.left = sortedListToBSTHelper(list, left, mid - 1);
        root.right = sortedListToBSTHelper(list, mid + 1, right);
        return root;
    }

以上是关于Leetcode——二叉树展开为链表 / 将有序数组转换为二叉搜索树 / 有序链表转换二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 114. 二叉树展开为链表(dfs)

[LeetCode] 114. 二叉树展开为链表 ☆☆☆(深度遍历)

LeetCode(114): 二叉树展开为链表

[LeetCode] 114. 二叉树展开为链表

二叉树展开为链表

二叉树展开为链表