Description

給定整數 n,回傳由 1 到 n 組成的所有結構不同的 BST。以任意順序回傳。

Example:

Input: n = 3 Output: [[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]

Intuition#

枚舉每個數字作為根,遞迴生成所有可能的左右子樹,再組合起來。

  • 與 96 題思路相同,但需要實際建構所有樹
  • 以 i 為根,左子樹由 [start, i-1] 生成,右子樹由 [i+1, end] 生成
  • 將所有左右子樹的組合配對,形成不同的 BST

Approaches#

1: 遞迴枚舉#

  • 概念: 枚舉區間內每個數字作為根,遞迴生成左右子樹列表,交叉組合
  • 時間複雜度: O(n * C(n)),C(n) 為卡塔蘭數
  • 空間複雜度: O(n * C(n))
class Solution {
    fun generateTrees(n: Int): List<TreeNode?> {
        if (n == 0) return emptyList()
        return generate(1, n)
    }

    private fun generate(start: Int, end: Int): List<TreeNode?> {
        if (start > end) return listOf(null)
        val result = mutableListOf<TreeNode?>()
        for (i in start..end) {
            val leftTrees = generate(start, i - 1)
            val rightTrees = generate(i + 1, end)
            for (left in leftTrees) {
                for (right in rightTrees) {
                    val root = TreeNode(i)
                    root.left = left
                    root.right = right
                    result.add(root)
                }
            }
        }
        return result
    }
}

⭐ 2: 遞迴 + Memoization#

  • 概念: 用 HashMap 快取已計算過的區間結果,避免重複遞迴
  • 時間複雜度: O(n * C(n))
  • 空間複雜度: O(n * C(n))
class Solution {
    private val memo = HashMap<Pair<Int, Int>, List<TreeNode?>>()

    fun generateTrees(n: Int): List<TreeNode?> {
        if (n == 0) return emptyList()
        memo.clear()
        return generate(1, n)
    }

    private fun generate(start: Int, end: Int): List<TreeNode?> {
        if (start > end) return listOf(null)
        val key = start to end
        memo[key]?.let { return it }

        val result = mutableListOf<TreeNode?>()
        for (i in start..end) {
            val leftTrees = generate(start, i - 1)
            val rightTrees = generate(i + 1, end)
            for (left in leftTrees) {
                for (right in rightTrees) {
                    val root = TreeNode(i)
                    root.left = left
                    root.right = right
                    result.add(root)
                }
            }
        }
        memo[key] = result
        return result
    }
}
注意:子樹共用問題

在 memoization 版本中,不同的樹可能共用相同的子樹節點。這在只讀取的情境下沒有問題,但如果需要修改樹的結構,則需要做深拷貝。LeetCode 的判題系統不會修改樹,所以共用是安全的。

🔑 Takeaways#

  • Pattern: 枚舉根 + 遞迴生成子樹 + 交叉組合,是生成所有 BST 的標準模板
  • 關鍵技巧: base case 回傳 listOf(null) 而非空列表,確保父節點能正確配對 null 子樹