LeetCode102-二叉树的层序遍历

题目描述

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:

二叉树:[3,9,20,null,null,15,7],
    3
   / \
  9  20
     / \
    15  7
返回其层次遍历结果:
[
  [3],
  [9,20],
  [15,7]
]

分析

先考虑只需要返回一个层序遍历结果而不用考虑到底是第几层。如上面只返回[3,9,20,15,7]
我们可以借助于一个队列Q:
首先把根元素放到Q中,while Q不为空时,一个个取出队首,将其不为null的子节点加入队列,直到队列为空。
用上面的例子,先把第一层3加入队列,然后3出队,加入子节点9,20,这是第二次;然后分别取出9,20,加入子节点15,7,这是第三次,取出15,7,他们没有子节点,没有元素入队,队为空,结束。
这个实现是比较简单的:

    //层序 : 利用队列
    public void layerTraversal(TreeNode root) {
        Queue<TreeNode> q= new LinkedBlockingQueue<>();
        TreeNode tmp ;
        q.add(root);
        while (q.size()>0){
            tmp = q.poll();
            System.out.print(tmp.val+" ");
            if (tmp.left!=null)q.add(tmp.left);
            if (tmp.right!=null)q.add(tmp.right);
        }
    }

实际上也可以利用数组,只不过稍微复杂点,原理一样。

    //层序 : 利用数组
    public void layerTraversal_iter_arr(TreeNode root){
        TreeNode []treeNodes= new TreeNode[100];//缺点:难以预测大小,要么浪费空间,要么不足报错
        int in=0,out=0;
        treeNodes[in++]=root;
        while (in>out){
            if (treeNodes[out]!=null){
                System.out.print(treeNodes[out].val+" ");
                treeNodes[in++]=treeNodes[out].left;
                treeNodes[in++]=treeNodes[out].right;
            }
            out++;
        }
    }

现在加需求了:需要考虑层次,即需要把不同层的分开。
我们如何在原来的基础上进行扩展了。
考虑前面使用队列的实现,一个队列存放了所有的元素,不能区分层次。如果使用两个队列存放,那么不同层不就可以交替使用,从而分开了吗?
比如有两个队列Q1、Q2,按上面的例子:

  1. 把root 3放到Q1
  2. Q1中3出队变空,子节点9,20放到Q2
  3. Q2出队变空,9,20的子节点15,7放到Q1
  4. Q1元素全部出队,没有子节点,两个队列都空了,程序结束。

那么现在就好实现了:

辅助双队列

    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();//结构

        LinkedList<TreeNode> q1 = new LinkedList<>();//Q1
        LinkedList<TreeNode> q2 = new LinkedList<>();//Q2
        LinkedList<TreeNode> cur,empty;//引用,分别指向当前使用的队列和空队列
        TreeNode tmp;
        if (root!=null)q1.add(root); //先把root加入Q1
        while (!q1.isEmpty() || !q2.isEmpty()){//至少一个队列不为空
            List<Integer> level = new LinkedList<>();
            if (q1.isEmpty()){ //使cur指向正在使用的队列,empty指向即将用于存储的空队列
                cur = q2;
                empty = q1;
            }else{
                cur = q1;
                empty= q2;
            }
            while (!cur.isEmpty()){//当前队列不为空,取出所有元素,把元素的非null子节点放到empty队列
                tmp = cur.pop();
                level.add(tmp.val);
                if (tmp.left!=null)empty.add(tmp.left);
                if (tmp.right!=null)empty.add(tmp.right);
            }
            res.add(level);
        }
        return res;
    }

性能:

执行用时 :1 ms, 在所有 Java 提交中击败了91.30%的用户
内存消耗 :39.8 MB, 在所有 Java 提交中击败了5.71%的用户

空间开销有点大,可以考虑使用数组代替队列。

看了下其他答案,上面的解法还可以优化。
我使用的是两个队列供不同层次交替使用从而分层,但是还有一种方式————添加分界节点。

辅助单队列+分界节点

在上面的实现基础上,层之间添加一个分界节点。
使用上面的例子说明下:
使用X表示分界节点

  1. 3入队,X入队
  2. 3,X出队,子节点9,20入队,X入队
  3. 9,20,X出队,子节点15,7入队,X入队
  4. 15,7,X出队,没有元素入队,X也不用入队,代表程序结束 (即:X只有在队列不为空的情况下入队)

实现:

    public List<List<Integer>> levelOrder(TreeNode root) {
        LinkedList<TreeNode> q = new LinkedList<>();//辅助队列
        TreeNode dummy = new TreeNode(Integer.MAX_VALUE);//分界节点

        List<List<Integer>> res = new LinkedList<>();//result
        List<Integer> level =new LinkedList<>();//层元素数组
        TreeNode tmp;//临时节点

        if (root==null)return res;
        q.add(root);
        q.add(dummy);//第一层和第二层之间的分界节点
        while (!q.isEmpty()){
            tmp = q.pop();
            if (tmp==dummy){//遇到分界节点,表示已经遍历完一层了
                res.add(level);
                level = new LinkedList<>();//下一层的存放数组
                if(!q.isEmpty()) q.add(dummy);//dummy只有在队列不为空的情况下入队,保证程序正常退出
            }else{//非分界节点
                level.add(tmp.val);
                if (tmp.left!=null)q.add(tmp.left);
                if (tmp.right!=null)q.add(tmp.right);
            }
        }
        return res;
    }

性能其实和上面差不多,空间其实也没省多少:元素都会放到队列中,双队列多一个队列的空间,单队列多一个分界节点。
两种思维而已。

递归实现

前面两种层序遍历都是迭代实现,其实也可以递归实现
递归实现的话由于不是一层一层的遍历的,那么怎么控制层数???
我们可以添加一个参数level,代表当前的层数,这样就可以把元素添加到对应的层中的list中了,不管你是怎么递归,只要把元素遍历完就好了。
实现:

    private List<List<Integer>> res = new LinkedList<>();//result
    public  List<List<Integer>> levelOrder_recur(TreeNode root){
        if (root==null)return res;
        helper(root,0);
        return res;
    }
    private void helper(TreeNode node,int level){
        if (res.size()==level)res.add(new LinkedList<>());//添加某层的List
        res.get(level).add(node.val);//获取level层,并添加元素
        if (node.left!=null)helper(node.left,level+1);//递归下去
        if (node.right!=null)helper(node.right,level+1);
    }

递归实现代码量最少,也很容易理解。

总结

总共写了三种方法,层序和递归,如果要归一个类的话,就是BFS和DFS

  1. BFS,双队列
  2. BFS,单队列+分界节点
  3. DFS,使用level参数记录层次

三种方法时间复杂度、空间复杂度都是O(N)

posted @ 2020-05-13 16:16  Edwin_Xu  阅读(146)  评论(0编辑  收藏  举报