Leetcode 演算法題解系列 - 二叉樹的層序遍歷

安歌發表於2022-02-01

本專題旨在分享刷Leecode過程發現的一些思路有趣或者有價值的題目。【當然是基於js進行解答】。
這道題應該算是二叉樹的基礎題,建議還是學一下,不難且經典

題目相關

  • 原題地址: https://leetcode-cn.com/probl...
  • 題目描述:

    從上到下按層列印二叉樹,同一層的節點按從左到右的順序列印,每一層列印到一行,例如,給定二叉樹: [3,9,20,null,null,15,7]
       3
       / \
      9  20
        /  \
       15   7

    返回其層次遍歷結果:

    [
      [3],
      [9,20],
      [15,7]
    ]

    Tips

    考慮某些同學可能比較少用js來刷leetcode,我們在這裡簡單介紹下,關於樹型別的資料輸入,在js裡的表示。 形如上題中的內容,給定二叉樹的輸入,其實並非一個陣列,而應該是如下所示: 每個節點是一個object

      const root = {
        val: 3,
        left: { // left表示當前節點的左側子節點
          val: 9,
          left: null, // 對照上圖可以看到 節點9沒有左右子節點
          right: null,
        },
        right: {
          val: 20,
          left: {
            val: 15, // 對照上圖可以看到 節點15沒有左右子節點
            left: null, 
            right: null,
          }, 
          right: {
            val: 7, // 對照上圖可以看到 節點7沒有左右子節點
            left: null, 
            right: null,
          },
        }
      }

思路解析

這道題比較基礎,其實考的是BFS+層序遍歷,怎麼說呢?
劃重點.png

首先 BFS(Breadth First Search)也就是廣度優先搜尋, 指的是在遍歷過程,每次總是優先遍歷當前節點的所有鄰接節點,將其放入一個佇列,比如上題的例子:

image.png

  • step1: 首先準備一個空佇列(也就是陣列) [ ] ,並且把根節點放入陣列中,也就是 [3]
  • step2: 先取出佇列的最前面節點(此時佇列只有節點3,取出之後佇列置空), 然後把它的所有鄰接節點(在二叉樹裡面也就是左右子節點)按順序加入訪問佇列,所以此時佇列元素為 [9  20] ,
  • step3: 繼續按照第二步的規則,取出節點9,並把9的子節點放入佇列(無子節點);再把節點20取出,並且把節點20的子節點存入佇列,此時佇列剩餘元素為 [15, 7] ;
  • step4: 繼續按照第二步規則, 取出節點15, 並把子節點放入佇列(無子節點);再把節點7取出,並且把節點7的子節點存入佇列,此時佇列剩餘元素為 [], 佇列為空 結束整個過程 ;

仔細觀察以上的步驟,你發現了嗎?
image.png

  1. 除了第一步初始化資料,後續的所有步驟 都在按照第2步的規則重複
  2. 觀察每一步的取出結果:

    1. step2, 取出[3];
    2. step3, 取出[9, 20];
    3. step4, 取出[15, 17];

    這裡每一步的取出結果 正好就是題設要求的每一層遍歷的結果。

  3. 整個過程結束條件,就是節點佇列為空。
    這道題講到這裡,其實大致思路就已經很清晰了,或者說,其實上面的步驟就已經是虛擬碼了(沒辦法,因為題目本身就非常典型-_-!)
  • 首先第2步的規則肯定就是主函式,因為它全程一直迴圈;
  • 其次,初始條件/結束條件分別對應的是 根節點入佇列和佇列為空;
  • 唯一的難點,在於如何優雅地處理【分層輸出】,而這一點 我建議大家直接看我最後貼地完整程式碼。
    image.png

完整程式碼

那麼其實就可以直接看程式碼了:

var levelOrder = function(root) {
    if (!root) { // 如果根節點都不存在 那直接結束 
        return [];
    }

    const nodes = [root]; // 初始化,對應前面分析裡地初始條件,
    const res = []; 

    while(nodes.length > 0) { // 外層迴圈 當節點佇列為空時結束
        const tempRes = [];
        let isEmpty = false;

        // 【注意點】這個內迴圈 也就是處理分層輸出地精髓所在,大家可以仔細揣摩下這個迴圈條件
        // 嘗試回答這裡為什麼不採用 “i=0 ;i<nodes.length; i++”的形式
        for(let i = nodes.length;i > 0 ; i--) { 
            const node = nodes.shift();
            tempRes.push(node.val);
            if(node.left) {
                nodes.push(node.left);
            }
            if(node.right) {
                nodes.push(node.right);
            }
        }
        res.push(tempRes);
       
    }
    return res;
 };

好了整體的題目算是基礎題, 就不畫動態圖了(畫圖很耗時), 但是想兩個問題給大家思考:

  1. 也就是程式碼塊裡的問題,為什麼內迴圈的條件不是 i=0 ;i<nodes.length; i++ ? 如果是,會怎麼樣?
  2. 如果本道題演變成輸出深度優先遍歷的結果,核心要點要怎麼改動?
    如果能夠回答出上面的兩個問題,那基本上就理解了這個知識點!

image.png

相關文章