6.14-二叉樹遍歷

七龙猪發表於2024-06-14

題目分類

題目分類大綱如下:

二叉樹大綱

二叉樹的種類

在我們解題過程中二叉樹有兩種主要的形式:滿二叉樹完全二叉樹

滿二叉樹

滿二叉樹:如果一棵二叉樹只有度為0的結點和度為2的結點,並且度為0的結點在同一層上,則這棵二叉樹為滿二叉樹。

如圖所示:

img

這棵二叉樹為滿二叉樹,也可以說深度為k,有2^k-1個節點的二叉樹。

完全二叉樹

什麼是完全二叉樹?

完全二叉樹的定義如下:在完全二叉樹中,除了最底層節點可能沒填滿外,其餘每層節點數都達到最大值,並且最下面一層的節點都集中在該層最左邊的若干位置。若最底層為第 h 層(h從1開始),則該層包含 1~ 2^(h-1) 個節點。

大家要自己看完全二叉樹的定義,很多同學對完全二叉樹其實不是真正的懂了。

一個典型的例子如題:

img

之前我們剛剛講過優先順序佇列其實是一個堆,堆就是一棵完全二叉樹,同時保證父子節點的順序關係。

二叉搜尋樹

前面介紹的樹,都沒有數值的,而二叉搜尋樹是有數值的了,二叉搜尋樹是一個有序樹

  • 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  • 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
  • 它的左、右子樹也分別為二叉排序樹

下面這兩棵樹都是搜尋樹

img

平衡二叉搜尋樹

平衡二叉搜尋樹:又被稱為AVL(Adelson-Velsky and Landis)樹,且具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹

如圖:

img

最後一棵 不是平衡二叉樹,因為它的左右兩個子樹的高度差的絕對值超過了1。

C++中map、set、multimap,multiset的底層實現都是平衡二叉搜尋樹,所以map、set的增刪操作時間時間複雜度是logn,注意我這裡沒有說unordered_map、unordered_set,其底層實現是雜湊表。

所以大家使用自己熟悉的程式語言寫演算法,一定要知道常用的容器底層都是如何實現的,最基本的就是map、set等等,否則自己寫的程式碼,自己對其效能分析都分析不清楚!

二叉樹的儲存方式

二叉樹可以鏈式儲存,也可以順序儲存。

那麼鏈式儲存方式就用指標, 順序儲存的方式就是用陣列

顧名思義就是順序儲存的元素在記憶體是連續分佈的,而鏈式儲存則是透過指標把分佈在各個地址的節點串聯一起。

鏈式儲存如圖:

img

鏈式儲存是大家很熟悉的一種方式,那麼我們來看看如何順序儲存呢?

其實就是用陣列來儲存二叉樹,順序儲存的方式如圖:

img

用陣列來儲存二叉樹如何遍歷的呢?

如果父節點的陣列下標是 i,那麼它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2

但是用鏈式表示的二叉樹,更有利於我們理解,所以一般我們都是用鏈式儲存二叉樹。

所以大家要了解,用陣列依然可以表示二叉樹。

二叉樹的遍歷方式

關於二叉樹的遍歷方式,要知道二叉樹遍歷的基本方式都有哪些。

一些同學用做了很多二叉樹的題目了,可能知道前中後序遍歷,可能知道層序遍歷,但是卻沒有框架。

我這裡把二叉樹的幾種遍歷方式列出來,大家就可以一一串起來了。

二叉樹主要有兩種遍歷方式:

  1. 深度優先遍歷:先往深走,遇到葉子節點再往回走。
  2. 廣度優先遍歷:一層一層的去遍歷。

這兩種遍歷是圖論中最基本的兩種遍歷方式,後面在介紹圖論的時候 還會介紹到。

那麼從深度優先遍歷和廣度優先遍歷進一步擴充,才有如下遍歷方式:

  • 深度優先遍歷
    • 前序遍歷(遞迴法,迭代法)
    • 中序遍歷(遞迴法,迭代法)
    • 後序遍歷(遞迴法,迭代法)
  • 廣度優先遍歷
    • 層次遍歷(迭代法)

這裡前中後,其實指的就是中間節點的遍歷順序.

看如下中間節點的順序,就可以發現,中間節點的順序就是所謂的遍歷方式

  • 前序遍歷:中左右
  • 中序遍歷:左中右
  • 後序遍歷:左右中

img

最後再說一說二叉樹中深度優先和廣度優先遍歷實現方式,我們做二叉樹相關題目,經常會使用遞迴的方式來實現深度優先遍歷,也就是實現前中後序遍歷,使用遞迴是比較方便的。

之前我們講棧與佇列的時候,就說過棧其實就是遞迴的一種實現結構,也就說前中後序遍歷的邏輯其實都是可以藉助棧使用遞迴的方式來實現的。

而廣度優先遍歷的實現一般使用佇列來實現,這也是佇列先進先出的特點所決定的,因為需要先進先出的結構,才能一層一層的來遍歷二叉樹。

二叉樹的定義

二叉樹有兩種儲存方式順序儲存,和鏈式儲存,順序儲存就是用陣列來存,這個定義沒啥可說的,我們來看看鏈式儲存的二叉樹節點的定義方式。

C++程式碼如下:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

大家會發現二叉樹的定義 和連結串列是差不多的,相對於連結串列 ,二叉樹的節點裡多了一個指標, 有兩個指標,指向左右孩子。

這裡要提醒大家要注意二叉樹節點定義的書寫方式。

在現場面試的時候 面試官可能要求手寫程式碼,所以資料結構的定義以及簡單邏輯的程式碼一定要鍛鍊白紙寫出來。

因為我們在刷leetcode的時候,節點的定義預設都定義好了,真到面試的時候,需要自己寫節點定義的時候,有時候會一臉懵逼!

總結

二叉樹是一種基礎資料結構,在演算法面試中都是常客,也是眾多資料結構的基石。

本篇我們介紹了二叉樹的種類、儲存方式、遍歷方式以及定義,比較全面的介紹了二叉樹各個方面的重點,幫助大家掃一遍基礎。

說到二叉樹,就不得不說遞迴,很多同學對遞迴都是又熟悉又陌生,遞迴的程式碼一般很簡短,但每次都是一看就會,一寫就廢。


二叉樹的遞迴遍歷

這次我們要好好談一談遞迴,為什麼很多同學看遞迴演算法都是“一看就會,一寫就廢”。

主要是對遞迴不成體系,沒有方法論,每次寫遞迴演算法 ,都是靠玄學來寫程式碼,程式碼能不能編過都靠運氣。

本篇將介紹前後中序的遞迴寫法,一些同學可能會感覺很簡單,其實不然,我們要透過簡單題目把方法論確定下來,有了方法論,後面才能應付複雜的遞迴。

這裡幫助大家確定下來遞迴演算法的三個要素。每次寫遞迴,都按照這三要素來寫,可以保證大家寫出正確的遞迴演算法!

  1. 確定遞迴函式的引數和返回值: 確定哪些引數是遞迴的過程中需要處理的,那麼就在遞迴函式里加上這個引數, 並且還要明確每次遞迴的返回值是什麼進而確定遞迴函式的返回型別。
  2. 確定終止條件: 寫完了遞迴演算法, 執行的時候,經常會遇到棧溢位的錯誤,就是沒寫終止條件或者終止條件寫的不對,作業系統也是用一個棧的結構來儲存每一層遞迴的資訊,如果遞迴沒有終止,作業系統的記憶體棧必然就會溢位。
  3. 確定單層遞迴的邏輯: 確定每一層遞迴需要處理的資訊。在這裡也就會重複呼叫自己來實現遞迴的過程。

好了,我們確認了遞迴的三要素,接下來就來練練手:

以下以前序遍歷為例:

  1. 確定遞迴函式的引數和返回值:因為要列印出前序遍歷節點的數值,所以引數裡需要傳入vector來放節點的數值,除了這一點就不需要再處理什麼資料了也不需要有返回值,所以遞迴函式返回型別就是void,程式碼如下:
void traversal(TreeNode* cur, vector<int>& vec)
  1. 確定終止條件:在遞迴的過程中,如何算是遞迴結束了呢,當然是當前序遍歷的節點是空了,那麼本層遞迴就要結束了,所以如果當前遍歷的這個節點是空,就直接return,程式碼如下:
if (cur == NULL) return;
  1. 確定單層遞迴的邏輯:前序遍歷是中左右的循序,所以在單層遞迴的邏輯,是要先取中節點的數值,程式碼如下:
vec.push_back(cur->val);    // 中
traversal(cur->left, vec);  // 左
traversal(cur->right, vec); // 右

單層遞迴的邏輯就是按照中左右的順序來處理的,這樣二叉樹的前序遍歷,基本就寫完了,再看一下完整程式碼:

前序遍歷:

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == NULL) return;
        vec.push_back(cur->val);    // 中
        traversal(cur->left, vec);  // 左
        traversal(cur->right, vec); // 右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

那麼前序遍歷寫出來之後,中序和後序遍歷就不難理解了,程式碼如下:

中序遍歷:

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

後序遍歷:

void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    traversal(cur->left, vec);  // 左
    traversal(cur->right, vec); // 右
    vec.push_back(cur->val);    // 中
}

此時大家可以做一做leetcode上三道題目,分別是:

  • 144.二叉樹的前序遍歷(opens new window)
  • 145.二叉樹的後序遍歷(opens new window)
  • 94.二叉樹的中序遍歷(opens new window)

二叉樹的迭代遍歷

為什麼可以用迭代法(非遞迴的方式)來實現二叉樹的前後中序遍歷呢?

我們在棧與佇列:匹配問題都是棧的強項 (opens new window)中提到了,遞迴的實現就是:每一次遞迴呼叫都會把函式的區域性變數、引數值和返回地址等壓入呼叫棧中,然後遞迴返回的時候,從棧頂彈出上一次遞迴的各項引數,所以這就是遞迴為什麼可以返回上一層位置的原因。

此時大家應該知道我們用棧也可以是實現二叉樹的前後中序遍歷了。

前序遍歷(迭代法)

我們先看一下前序遍歷。

前序遍歷是中左右,每次先處理的是中間節點,那麼先將根節點放入棧中,然後將右孩子加入棧,再加入左孩子。

為什麼要先加入 右孩子,再加入左孩子呢? 因為這樣出棧的時候才是中左右的順序。

動畫如下:

二叉樹前序遍歷(迭代法)

不難寫出如下程式碼: (注意程式碼中空節點不入棧

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            result.push_back(node->val);
            if (node->right) st.push(node->right);           // 右(空節點不入棧)
            if (node->left) st.push(node->left);             // 左(空節點不入棧)
        }
        return result;
    }
};

此時會發現貌似使用迭代法寫出前序遍歷並不難,確實不難。

此時是不是想改一點前序遍歷程式碼順序就把中序遍歷搞出來了?

其實還真不行!

但接下來,再用迭代法寫中序遍歷的時候,會發現套路又不一樣了,目前的前序遍歷的邏輯無法直接應用到中序遍歷上。

中序遍歷(迭代法)

為了解釋清楚,我說明一下 剛剛在迭代的過程中,其實我們有兩個操作:

  1. 處理:將元素放進result陣列中
  2. 訪問:遍歷節點

分析一下為什麼剛剛寫的前序遍歷的程式碼,不能和中序遍歷通用呢,因為前序遍歷的順序是中左右,先訪問的元素是中間節點,要處理的元素也是中間節點,所以剛剛才能寫出相對簡潔的程式碼,因為要訪問的元素和要處理的元素順序是一致的,都是中間節點。

那麼再看看中序遍歷,中序遍歷是左中右,先訪問的是二叉樹頂部的節點,然後一層一層向下訪問,直到到達樹左面的最底部,再開始處理節點(也就是在把節點的數值放進result陣列中),這就造成了處理順序和訪問順序是不一致的。

那麼在使用迭代法寫中序遍歷,就需要借用指標的遍歷來幫助訪問節點,棧則用來處理節點上的元素。

動畫如下:

二叉樹中序遍歷(迭代法)

中序遍歷,可以寫出如下程式碼:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) { // 指標來訪問節點,訪問到最底層
                st.push(cur); // 將訪問的節點放進棧
                cur = cur->left;                // 左
            } else {
                cur = st.top(); // 從棧裡彈出的資料,就是要處理的資料(放進result陣列裡的資料)
                st.pop();
                result.push_back(cur->val);     // 中
                cur = cur->right;               // 右
            }
        }
        return result;
    }
};

後序遍歷(迭代法)

再來看後序遍歷,先序遍歷是中左右,後續遍歷是左右中,那麼我們只需要調整一下先序遍歷的程式碼順序,就變成中右左的遍歷順序,然後在反轉result陣列,輸出的結果順序就是左右中了,如下圖:

前序到後序

所以後序遍歷只需要前序遍歷的程式碼稍作修改就可以了,程式碼如下:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> st;
        vector<int> result;
        if (root == NULL) return result;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            result.push_back(node->val);
            if (node->left) st.push(node->left); // 相對於前序遍歷,這更改一下入棧順序 (空節點不入棧)
            if (node->right) st.push(node->right); // 空節點不入棧
        }
        reverse(result.begin(), result.end()); // 將結果反轉之後就是左右中的順序了
        return result;
    }
};

總結

此時我們用迭代法寫出了二叉樹的前後中序遍歷,大家可以看出前序/後序和中序是完全兩種程式碼風格,並不像遞迴寫法那樣程式碼稍做調整,就可以實現前後中序。

這是因為前序遍歷中訪問節點(遍歷節點)和處理節點(將元素放進result陣列中)可以同步處理,但是中序就無法做到同步!

上面這句話,可能一些同學不太理解,建議自己親手用迭代法,先寫出來前序,再試試能不能寫出中序,就能理解了。

那麼問題又來了,難道 二叉樹前後中序遍歷的迭代法實現,就不能風格統一麼(即前序遍歷 改變程式碼順序就可以實現中序 和 後序)?

二叉樹的統一迭代法

思路

我們以中序遍歷為例,使用棧的話,無法同時解決訪問節點(遍歷節點)和處理節點(將元素放進結果集)不一致的情況

那我們就將訪問的節點放入棧中,把要處理的節點也放入棧中但是要做標記。

如何標記呢,就是要處理的節點放入棧之後,緊接著放入一個空指標作為標記。 這種方法也可以叫做標記法

迭代法中序遍歷

中序遍歷程式碼如下:(詳細註釋)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop(); // 將該節點彈出,避免重複操作,下面再將右中左節點新增到棧中
                if (node->right) st.push(node->right);  // 新增右節點(空節點不入棧)

                st.push(node);                          // 新增中節點 
                st.push(NULL); // 中節點訪問過,但是還沒有處理,加入空節點做為標記。

                if (node->left) st.push(node->left);    // 新增左節點(空節點不入棧)
            } else { // 只有遇到空節點的時候,才將下一個節點放進結果集
                st.pop();           // 將空節點彈出
                node = st.top();    // 重新取出棧中元素
                st.pop();
                result.push_back(node->val); // 加入到結果集
            }
        }
        return result;
    }
};

看程式碼有點抽象我們來看一下動畫(中序遍歷):

中序遍歷迭代(統一寫法)

可以看出我們將訪問的節點直接加入到棧中,但如果是處理的節點則後面放入一個空節點, 這樣只有空節點彈出的時候,才將下一個節點放進結果集。

此時我們再來看前序遍歷程式碼。

迭代法前序遍歷

迭代法前序遍歷程式碼如下: (注意此時我們和中序遍歷相比僅僅改變了兩行程式碼的順序)

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
                st.push(node);                          // 中
                st.push(NULL);
            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

迭代法後序遍歷

後續遍歷程式碼如下: (注意此時我們和中序遍歷相比僅僅改變了兩行程式碼的順序)

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        stack<TreeNode*> st;
        if (root != NULL) st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);

                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左

            } else {
                st.pop();
                node = st.top();
                st.pop();
                result.push_back(node->val);
            }
        }
        return result;
    }
};

總結

此時我們寫出了統一風格的迭代法,不用在糾結於前序寫出來了,中序寫不出來的情況了。

但是統一風格的迭代法並不好理解,而且想在面試直接寫出來還有難度的。

所以大家根據自己的個人喜好,對於二叉樹的前中後序遍歷,選擇一種自己容易理解的遞迴和迭代法。


二叉樹的層序遍歷

學會二叉樹的層序遍歷,可以一口氣打完以下十題:

  • 102.二叉樹的層序遍歷(opens new window)
  • 107.二叉樹的層次遍歷II(opens new window)
  • 199.二叉樹的右檢視(opens new window)
  • 637.二叉樹的層平均值(opens new window)
  • 429.N叉樹的層序遍歷(opens new window)
  • 515.在每個樹行中找最大值(opens new window)
  • 116.填充每個節點的下一個右側節點指標(opens new window)
  • 117.填充每個節點的下一個右側節點指標II(opens new window)
  • 104.二叉樹的最大深度(opens new window)
  • 111.二叉樹的最小深度

102.二叉樹的層序遍歷

題意描述:

給你一個二叉樹,請你返回其按層序遍歷得到的節點值。(即逐層地,從左到右訪問所有節點)。

示例 1:

img

輸入:root = [3,9,20,null,null,15,7]
輸出:[[3],[9,20],[15,7]]

示例 2:

輸入:root = [1]
輸出:[[1]]

示例 3:

輸入:root = []
輸出:[]

提示:

  • 樹中節點數目在範圍 [0, 2000]
  • -1000 <= Node.val <= 1000

思路:

層序遍歷一個二叉樹。就是從左到右一層一層的去遍歷二叉樹。這種遍歷的方式和我們之前講過的都不太一樣。

需要借用一個輔助資料結構即佇列來實現,佇列先進先出,符合一層一層遍歷的邏輯,而用棧先進後出適合模擬深度優先遍歷也就是遞迴的邏輯。

而這種層序遍歷方式就是圖論中的廣度優先遍歷,只不過我們應用在二叉樹上。

使用佇列實現二叉樹廣度優先遍歷,動畫如下:

102二叉樹的層序遍歷

這樣就實現了層序從左到右遍歷二叉樹。

程式碼如下:這份程式碼也可以作為二叉樹層序遍歷的模板,打十個就靠它了

AC程式碼:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec;
            // 這裡一定要使用固定大小size,不要使用que.size(),因為que.size是不斷變化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};
# 遞迴法
class Solution {
public:
    void order(TreeNode* cur, vector<vector<int>>& result, int depth)
    {
        if (cur == nullptr) return;
      //當二維陣列行數等於深度時,即遍歷到第幾層時新建立一個行
        if (result.size() == depth) result.push_back(vector<int>());

        result[depth].push_back(cur->val);
      
        order(cur->left, result, depth + 1);
        order(cur->right, result, depth + 1);
    }
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        int depth = 0;
        order(root, result, depth);
        return result;
    }
};

107.二叉樹的層次遍歷 II

題意描述:

給你二叉樹的根節點 root ,返回其節點值 自底向上的層序遍歷 。 (即按從葉子節點所在層到根節點所在的層,逐層從左向右遍歷)

示例 1:

img

輸入:root = [3,9,20,null,null,15,7]
輸出:[[15,7],[9,20],[3]]

示例 2:

輸入:root = [1]
輸出:[[1]]

示例 3:

輸入:root = []
輸出:[]

提示:

  • 樹中節點數目在範圍 [0, 2000]
  • -1000 <= Node.val <= 1000

思路:

上面層序遍歷+reverse(res.begin() , res.end()) 按行翻轉即可。

AC程式碼:

class Solution{
  public:
  void order(TreeNode* cur , vector<vector<int>>& res , int depth){
    if(cur == NULL)  return;
    if(res.size() == depth) res.push_back(vector<int>());
    res[depth].push_back(cur -> val);

    order(cur -> left , res , depth + 1);
    order(cur -> right , res , depth + 1);
  }

  vector<vector<int>> levelOrderBottom(TreeNode* root){
    vector<vector<int>> res;
    int depth = 0;
    order(root , res , depth);
    reverse(res.begin(),res.end());
    return res;
  }
};

199.二叉樹的右檢視

題意描述:

給定一個二叉樹的 根節點 root,想象自己站在它的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值。

示例 1:

img

輸入: [1,2,3,null,5,null,4]
輸出: [1,3,4]

示例 2:

輸入: [1,null,3]
輸出: [1,3]

示例 3:

輸入: []
輸出: []

提示:

  • 二叉樹的節點個數的範圍是 [0,100]
  • -100 <= Node.val <= 100

思路:

層序遍歷的時候,判斷是否遍歷到單層的最後面的元素,如果是,就放進result陣列中,隨後返回result就可以了。

AC程式碼:

BFS:
class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<int> result;
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (i == (size - 1)) result.push_back(node->val); // 將每一層的最後元素放入result陣列中
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return result;
    }
};
DFS:
class Solution {
    void dfs(TreeNode* root, int depth, vector<int>& ans) {
        if (!root) return;
        if (depth == ans.size()) ans.push_back(root->val);
      //這裡相當於省略了一步 Depth++ 合寫到下面一行,執行到dfs(left···)時
      //已經是++之後的depth了
        dfs(root->right, ++depth, ans);
        dfs(root->left, depth, ans);
    }
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> ans;
        dfs(root, 0, ans);
        return ans;
    }
};

637.二叉樹的層平均值

題意描述:

給定一個非空二叉樹的根節點 root , 以陣列的形式返回每一層節點的平均值。與實際答案相差 1e-5 以內的答案可以被接受。

示例 1:

img

輸入:root = [3,9,20,null,null,15,7]
輸出:[3.00000,14.50000,11.00000]
解釋:第 0 層的平均值為 3,第 1 層的平均值為 14.5,第 2 層的平均值為 11 。
因此返回 [3, 14.5, 11] 。

示例 2:

img

輸入:root = [3,9,20,15,7]
輸出:[3.00000,14.50000,11.00000]

提示:

  • 樹中節點數量在 [1, 104] 範圍內
  • -2^31 <= Node.val <= 2^31 - 1

思路:

本題就是層序遍歷的時候把一層求個總和在取一個均值。

AC程式碼:

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
      //注意這裡的vector型別,要根據函式型別定義返回型別double
        vector<double> result;
        while (!que.empty()) {
            int size = que.size();
            double sum = 0; // 統計每一層的和,這裡sum也得保留小數,定義為double型
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                sum += node->val;
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(sum / size); // 將每一層均值放進結果集
        }
        return result;
    }
};

相關文章