A *full binary tree* is a binary tree where each node has exactly 0 or 2 children.
Return a list of all possible full binary trees with N
nodes. Each element of the answer is the root node of one possible tree.
Each node
of each tree in the answer must have node.val = 0
.
You may return the final list of trees in any order.
Example 1:
Input: 7
Output: [[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]
Explanation:
Note:
1 <= N <= 20
這道題給了一個數字N,讓我們生成所有包含N個結點的滿二叉樹。所謂的滿二叉樹,就是每個結點一定會有0個或2兩個子結點,換句話說,子結點必須成對出現,注意跟完全二叉樹區分。現在我們有N個結點可以使用,若我們仔細觀察,可以發現,所有的滿二叉樹的結點總數都是奇數,所以只要當N為偶數的時候,一定返回的是空數組,這個可以當作一個剪枝放在開頭。下面我們就來考慮當N是奇數時,如何生成不同的滿二叉樹。先從最簡單的開始,當 N=1 時,就只有一個根結點,當 N=3 時,也只有一種情況,根結點和左右子結點,當 N=5 時,就有如下兩種情況:
0
/ \
0 0
/ \
0 0
0
/ \
0 0
/ \
0 0
我們可以看出來就是在 N=3 的情況下再多加兩個結點,這兩個結點可以都在左子結點下,或者都在右子結點下。但是當 N=7 的時候就比較 tricky 了,也可以看作是在 N=5 的情況下生成的,我們可以把多余出來的兩個結點分別加到上面兩棵樹的任意一個葉結點下方,但可能你會有疑問,上面的兩棵樹各自都有三個葉結點,每個都加的話,不就應該有6種情況了么,其實只有5種,因為其中有兩種情況是重合的,即在第一棵樹的最右葉結點下添加,跟在第二棵樹的最左葉結點下添加后得到的完全二叉樹是一樣的,所以總共只有5種組合。
講到這里,身為讀者的你可能還是比較迷茫,到底該如何用代碼來生成,我們再換一種思維方式,若總共有N個結點可以分配,那么除去根結點,左右子樹一共可以分配 N-1 個結點,由於N一定是奇數,那么 N-1 一定就是偶數,所以左右子樹需要共同來分配這 N-1 個結點。又因為滿二叉樹的子樹也必須是滿二叉樹,所以每個子樹的結點總數也應該是奇數,由於 N-1 是偶數,所以這 N-1 個結點不可能全部給其中的一個子樹,即左右子樹至少有一個結點,那么實際上就是把 N-1 這個偶數拆分成任意兩個奇數之和,比如p和q,滿足 p+q = N-1,且p,q均為奇數,然后對其分別對p和q調用遞歸函數,得到兩個數組,數組里面的就是所有可能情況的左右子樹的根結點。之后要做的就是從這兩個數組中任意取兩個結點,加到一個新建的 cur 結點的左右子結點上,然后將 cur 結點存入結果 res 中。這種處理方法跟之前的那兩道題 Unique Binary Search Trees II,Different Ways to Add Parentheses 一模一樣,若大家眼睛夠尖的話,可以看出來這其實也是 卡塔蘭數 Catalan Numbe,參見代碼如下:
解法一:
class Solution {
public:
vector<TreeNode*> allPossibleFBT(int N) {
if (N % 2 == 0) return {};
if (N == 1) return {new TreeNode(0)};
vector<TreeNode*> res;
for (int i = 1; i < N; i += 2) {
vector<TreeNode*> left = allPossibleFBT(i), right = allPossibleFBT(N - i - 1);
for (auto a : left) {
for (auto b : right) {
TreeNode *cur = new TreeNode(0);
cur->left = a;
cur->right = b;
res.push_back(cur);
}
}
}
return res;
}
};
我們可以通過使用一個 HashMap 來避免重復計算,從而提升運算速度,建立每個值跟其對應的滿二叉樹的根結點數組之間的映射,那么在遞歸函數中,判定完了偶數跟1的情況后,就看當前N值是否已經計算過了,是的話,直接從 HashMap 中取數組,否則就進行和上面一樣的運算,最后在返回前要將結果存入 HashMap 中,參見代碼如下:
解法二:
class Solution {
public:
unordered_map<int, vector<TreeNode*>> m;
vector<TreeNode*> allPossibleFBT(int N) {
if (N % 2 == 0) return {};
if (N == 1) return {new TreeNode(0)};
if (m.count(N)) return m[N];
vector<TreeNode*> res;
for (int i = 1; i < N; i += 2) {
vector<TreeNode*> left = allPossibleFBT(i), right = allPossibleFBT(N - i - 1);
for (auto a : left) {
for (auto b : right) {
TreeNode *cur = new TreeNode(0);
cur->left = a;
cur->right = b;
res.push_back(cur);
}
}
}
return m[N] = res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/894
類似題目:
Different Ways to Add Parentheses
參考資料:
https://leetcode.com/problems/all-possible-full-binary-trees/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)