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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章