← 題庫 / Archive
2026-08-14 TI150 Medium TreeBreadth-First SearchBinary Tree

102. Binary Tree Level Order Traversal

題目 / Problem

中文: 給定一棵二元樹的根節點 root,請回傳它的「層序走訪」結果:也就是從上到下、一層一層地,把每一層的節點值由左到右收集起來,每一層放進一個獨立的子陣列。

English: Given the root of a binary tree, return the level order traversal of its nodes' values — that is, go top to bottom, one level at a time, collecting each level's values from left to right, with each level in its own sub-array.

Constraints / 限制: - 節點數量在 [0, 2000] 之間(可能是空樹)。The number of nodes is in the range [0, 2000] (the tree may be empty). - -1000 <= Node.val <= 1000

Worked example / 範例:

Input:  root = [3,9,20,null,null,15,7]

        3          <- level 0
       / \
      9  20        <- level 1
        /  \
       15   7      <- level 2

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

名詞解釋 / Glossary

  • 二元樹 / Binary tree:一種樹狀資料結構,每個節點最多有兩個子節點,分別叫「左子節點」left 和「右子節點」right。A tree where each node has at most two children, called left and right.
  • 節點 / Node:樹裡的一個元素,含有一個值 val,以及指向左右子節點的指標。One element of the tree, holding a value val and pointers to its two children.
  • 根節點 / Root:樹最頂端、沒有父節點的那個節點;整棵樹都從它往下延伸。The topmost node with no parent; the whole tree hangs from it.
  • 層 / Level:距離根節點步數相同的一群節點。根是第 0 層,根的子節點是第 1 層,依此類推。All nodes the same number of steps from the root. Root is level 0, its children level 1, and so on.
  • 廣度優先搜尋 / BFS (Breadth-First Search):一種走訪方式,先把某一層全部走完,才進入下一層——正好符合「一層一層」的需求。A traversal that finishes an entire level before moving to the next — exactly what "level by level" needs.
  • 佇列 / Queue:一種「先進先出」(FIFO) 的容器:先放進去的元素先被取出,就像排隊。我們用它來記住「還沒處理的節點」。A first-in-first-out container: the earliest item added is the first removed, like a line at a shop. We use it to remember nodes not yet processed.
  • 指標 / Pointer (C):一個變數,裡面存的是另一個東西的「記憶體位址」。node->left 表示「順著 node 這個指標,去拿它的 left 欄位」。A variable holding the memory address of something else. node->left means "follow the node pointer and read its left field."

思路

最直覺的想法可能是先算出樹有幾層、再一層一層去撈,但那要重複走訪很多次,很浪費。真正貼合「一層一層由左到右」的工具是「廣度優先搜尋 (BFS)」,而 BFS 的核心零件就是「佇列」。佇列是先進先出的:先排隊的先被服務。做法是:先把根節點放進佇列。接著重複一個大迴圈,只要佇列不是空的就繼續。每一輪迴圈開始時,佇列裡「剛好」裝著目前這一層的所有節點——這是關鍵不變性 (invariant)。我們先記下此刻佇列的長度 size,這個數字就是這一層的節點個數。然後我們正好取出 size 個節點:每取出一個,就把它的值放進「本層陣列」,並把它的左、右子節點(如果存在)推進佇列尾端。當這 size 個都處理完,佇列裡剩下的就恰好是「下一層」的所有節點,於是我們把本層陣列收進答案,進入下一輪。為什麼先記 size 很重要?因為迴圈進行中我們一直在往佇列加新節點(下一層),如果不先鎖定數量,就會把下一層也混進這一層。空樹時佇列一開始就是空的,大迴圈直接不執行,回傳空陣列,自然正確。

The naive idea — count the levels first, then fetch each level separately — forces you to walk the tree many times, which is wasteful. The tool that naturally matches "level by level, left to right" is Breadth-First Search (BFS), and its key component is a queue (first-in-first-out). The plan: push the root into the queue. Then loop as long as the queue is non-empty. At the start of each loop iteration, the queue holds exactly all the nodes of the current level — that is the crucial invariant. We snapshot the queue's current length into size; that count is how many nodes are on this level. We then pop exactly size nodes: for each one we append its value to the current level's array and push its left and right children (when they exist) onto the back of the queue. After those size pops, whatever remains in the queue is precisely the next level, so we attach the current level's array to the answer and repeat. Why snapshot size first? Because while we process this level we keep adding the next level's nodes; if we didn't lock the count in, we'd blur the two levels together. For an empty tree the queue starts empty, the loop never runs, and we correctly return an empty result.

逐步走查 / Walkthrough

Input: root = [3,9,20,null,null,15,7]. We write the queue front on the left. answer starts empty.

Step / 步驟 Queue at start (this level) size Pop & record / 取出並記錄 Children pushed / 推入子節點 answer after
Init [3] []
Level 0 [3] 1 pop 3 → level=[3] push 9, 20 [[3]]
Level 1 [9,20] 2 pop 9 → [9]; pop 20 → [9,20] 9 has no children; 20 pushes 15, 7 [[3],[9,20]]
Level 2 [15,7] 2 pop 15 → [15]; pop 7 → [15,7] 15 and 7 have no children [[3],[9,20],[15,7]]
End [] (empty) loop stops / 迴圈結束 [[3],[9,20],[15,7]]

Notice how at Level 1 we recorded size = 2 before popping — so even though popping 20 adds two new nodes (15, 7) to the queue, they wait for Level 2 and don't sneak into Level 1. 注意在 Level 1 我們「先」把 size 鎖成 2,因此即使處理 20 時加入了 15、7,它們也只會留到 Level 2,不會混進本層。

Solution — C

/*
 * 演算法 / Algorithm: BFS with a queue (廣度優先搜尋 + 佇列).
 * 用陣列當佇列,一次處理一整層;每層開始時先鎖定該層節點數 size,
 * We use an array as a FIFO queue and process one full level per outer loop,
 * snapshotting the level's node count (size) before popping so levels don't mix.
 */

/* LeetCode 提供的節點定義(此處僅為說明,實際由平台給出)
 * struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; };
 */

/**
 * 回傳一個 int** (指向多個 int 陣列的指標),每個子陣列是一層。
 * Returns int** : an array of int arrays, one per level.
 * returnSize:        寫回總共有幾層 / how many levels (rows) there are.
 * returnColumnSizes: 寫回每一層各有幾個元素 / length of each row.
 */
int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes) {
    // 樹最多 2000 個節點,先為結果與佇列預留足夠空間。
    // At most 2000 nodes; reserve enough room for results and the queue up front.
    int** answer = (int**)malloc(sizeof(int*) * 2000);       // answer[i] 指向第 i 層的值陣列 / answer[i] = values of level i
    *returnColumnSizes = (int*)malloc(sizeof(int) * 2000);   // 每層長度的陣列 / array holding each level's length
    *returnSize = 0;                                          // 目前收集到的層數,先設 0 / levels collected so far

    // 空樹:沒有任何層,直接回傳。 Empty tree: no levels, return immediately.
    if (root == NULL) return answer;

    // 用固定大小陣列當佇列。head 指向下一個要取出的位置,tail 指向下一個要放入的位置。
    // A fixed-size array as the queue: head = next to pop, tail = next to push.
    struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 2000);
    int head = 0, tail = 0;                                   // 佇列一開始是空的 / queue starts empty

    queue[tail++] = root;                                     // 把根節點放進佇列尾 / enqueue the root (tail++ 先用再加一 / use then increment)

    // 只要佇列還有節點,就代表還有一層要處理。
    // While the queue is non-empty, there is another level to process.
    while (head < tail) {
        int size = tail - head;                              // 關鍵:先鎖定本層節點數 / snapshot THIS level's node count
        int* level = (int*)malloc(sizeof(int) * size);       // 本層的值陣列,長度剛好 size / this level's values, length size
        int k = 0;                                            // k 是本層陣列的下一個寫入位置 / next write slot in level[]

        // 正好取出 size 個節點,就是完整的一層。
        // Pop exactly `size` nodes — that is one complete level.
        for (int i = 0; i < size; i++) {
            struct TreeNode* node = queue[head++];           // 取出佇列最前面的節點 / dequeue front node (head++ 前進 / advance head)
            level[k++] = node->val;                          // 記下它的值;node->val 是「順著指標拿 val 欄位」/ record its value (-> dereferences the pointer)

            // 把左右子節點推進佇列,它們屬於「下一層」。
            // Push children onto the queue; they belong to the NEXT level.
            if (node->left)  queue[tail++] = node->left;      // 有左子節點才放 / enqueue left child if it exists
            if (node->right) queue[tail++] = node->right;     // 有右子節點才放 / enqueue right child if it exists
        }

        answer[*returnSize] = level;                         // 把本層陣列存進結果 / store this level's array
        (*returnColumnSizes)[*returnSize] = size;            // 記下本層長度 / record this level's length
        (*returnSize)++;                                     // 層數加一 / one more level collected
    }

    free(queue);                                             // 佇列用完了,釋放記憶體 / done with the queue, free it
    return answer;                                           // 回傳所有層 / return all levels
}

Solution — C++

/*
 * 演算法 / Algorithm: BFS with std::queue (廣度優先搜尋 + 佇列).
 * 每一輪外層迴圈用 q.size() 鎖定當前層的節點數,處理完剛好留下下一層。
 * Each outer loop uses q.size() to lock the current level's count, leaving
 * exactly the next level in the queue afterward.
 */

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> answer;          // 最終答案,每個元素是一層 / result; each element is one level
        if (root == nullptr) return answer;  // 空樹直接回空陣列 / empty tree → empty result

        queue<TreeNode*> q;                   // std::queue 是先進先出容器 / std::queue is a FIFO container
        q.push(root);                         // 根節點入列 / enqueue the root

        // 佇列非空代表還有下一層要處理。
        // A non-empty queue means another level remains.
        while (!q.empty()) {
            int size = q.size();              // 關鍵:先記本層節點數 / snapshot THIS level's node count
            vector<int> level;                // 收集本層的值 / values for this level
            level.reserve(size);              // 預留空間避免多次重新配置 / reserve to avoid reallocations (小優化 / minor optimization)

            // 正好處理 size 個節點 = 完整一層。
            // Process exactly `size` nodes = one full level.
            for (int i = 0; i < size; ++i) {
                TreeNode* node = q.front();   // 看佇列最前面的節點 / read the front node
                q.pop();                      // 把它移出佇列 / remove it from the queue
                level.push_back(node->val);   // 記下它的值 / record its value

                // 子節點屬於下一層,推到佇列尾。
                // Children belong to the next level; push them to the back.
                if (node->left)  q.push(node->left);   // 有左子節點才入列 / enqueue left child if present
                if (node->right) q.push(node->right);  // 有右子節點才入列 / enqueue right child if present
            }

            answer.push_back(move(level));    // 本層完成,收進答案;move 避免複製 / append level; move() transfers instead of copying
        }

        return answer;                        // 回傳逐層結果 / return level-by-level result
    }
};

複雜度 / Complexity

  • Time: O(n) — 每個節點剛好被「入列一次、出列一次」,各種操作都是常數時間,所以總時間和節點數 n 成正比。n 指樹的節點總數。Each node is enqueued once and dequeued once, each operation is O(1), so total work is proportional to the number of nodes n.
  • Space: O(n) — 最壞情況下佇列同時裝著最寬那一層的節點(滿二元樹的最後一層可達約 n/2 個),加上回傳結果本身也要 O(n)。In the worst case the queue holds the widest level (up to ~n/2 nodes in a full tree), and the output itself is O(n).

Pitfalls & Edge Cases

  • 空樹 / Empty tree (root == NULL):一定要先檢查再進迴圈,否則會對空指標做 node->left 而崩潰。Check for NULL first; otherwise dereferencing a null pointer crashes. 兩份程式都在最前面就 return
  • 忘記先鎖定 size / Forgetting to snapshot size:如果在迴圈條件裡直接用「當前佇列長度」,處理本層時新加入的下一層節點會被算進來,導致層與層混在一起。Read the level count into a local size before the inner loop — reading the live queue length would fold the next level into the current one.
  • C 的回傳三件套 / C's three return outputsreturnSize(幾層)、returnColumnSizes(每層幾個)、以及回傳值本身,三者必須一致,少填一個 LeetCode 就判錯。All three must agree; forgetting returnColumnSizes is a common wrong-answer cause.
  • 只有一個節點 / Single node ([1]):外層迴圈只跑一次,答案是 [[1]],不是 [1]——注意每一層都要包成子陣列。The answer is [[1]], not [1]; every level is wrapped in its own sub-array.
  • 記憶體 / Memory (C):這裡的佇列在結束前 free,但每層的 level 陣列與 answer 需交還給 LeetCode,不可 free。LeetCode takes ownership of the returned arrays, so free only the temporary queue — never the arrays you return.
  • 值可能為負 / Negative valuesNode.val 可低到 -1000,用 int 儲存完全沒問題,不必特別處理。Values can be negative; plain int handles them fine.