Description

給定一棵二元樹的根節點,回傳樹的最大寬度。最大寬度是所有層中,最左和最右非 null 節點之間的寬度(包含中間的 null 節點)。

Example:

Input: root = [1,3,2,5,3,null,9] Output: 4

Intuition#

為每個節點編號,每層的寬度就是最右編號減最左編號加一。

  • 用完全二元樹的索引方式:根為 0,左子為 2i+1,右子為 2i+2
  • 每一層記錄最左和最右的索引,差值 + 1 就是該層寬度
  • 注意:索引可能溢位,需要做偏移處理(每層減去該層最左的索引)

Approaches#

1: BFS + 索引編號#

  • 概念: 層序遍歷,為每個節點編號,每層計算最左與最右索引的差
  • 時間複雜度: O(n)
  • 空間複雜度: O(n)
class Solution {
    fun widthOfBinaryTree(root: TreeNode?): Int {
        if (root == null) return 0
        var maxWidth = 0
        val queue: Queue<Pair<TreeNode, Int>> = LinkedList()
        queue.offer(root to 0)
        while (queue.isNotEmpty()) {
            val size = queue.size
            val leftIdx = queue.peek().second
            var rightIdx = leftIdx
            for (i in 0 until size) {
                val (node, idx) = queue.poll()
                val normalizedIdx = idx - leftIdx // 防止溢位
                rightIdx = normalizedIdx
                node.left?.let { queue.offer(it to normalizedIdx * 2 + 1) }
                node.right?.let { queue.offer(it to normalizedIdx * 2 + 2) }
            }
            maxWidth = maxOf(maxWidth, rightIdx + 1)
        }
        return maxWidth
    }
}

⭐ 2: DFS + 記錄每層最左索引#

  • 概念: DFS 遍歷,用列表記錄每一層第一次遇到的索引(最左),後續節點的索引減去最左索引即為寬度
  • 時間複雜度: O(n)
  • 空間複雜度: O(h),h 為樹高
class Solution {
    private var maxWidth = 0
    private val leftMost = mutableListOf<Long>()

    fun widthOfBinaryTree(root: TreeNode?): Int {
        maxWidth = 0
        leftMost.clear()
        dfs(root, 0, 0L)
        return maxWidth
    }

    private fun dfs(node: TreeNode?, depth: Int, index: Long) {
        if (node == null) return
        if (depth == leftMost.size) {
            leftMost.add(index)
        }
        val width = (index - leftMost[depth] + 1).toInt()
        maxWidth = maxOf(maxWidth, width)
        dfs(node.left, depth + 1, index * 2 + 1)
        dfs(node.right, depth + 1, index * 2 + 2)
    }
}

🔑 Takeaways#

  • Pattern: 利用完全二元樹的索引編號來計算寬度,本質是數學映射
  • 關鍵技巧: 每層索引需要做偏移(減去該層最左索引),避免深度較大時索引溢位