題目鏈接:
不同的二叉查找樹:http://www.lintcode.com/zh-cn/problem/unique-binary-search-trees/
不同的二叉查找樹 II:http://www.lintcode.com/zh-cn/problem/unique-binary-search-trees-ii/
不同形態二叉樹的數目:
樣例
給出n = 3,有5種不同形態的二叉查找樹:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
分析
可以分析,當n=1時,只有1個根節點,則只能組成1種形態的二叉樹,令n個節點可組成的二叉樹數量表示為h(n),則h(1)=1; h(0)=0;
當n=2時,1個根節點固定,還有2-1個節點。這一個節點可以分成(1,0),(0,1)兩組。即左邊放1個,右邊放0個;或者左邊放0個,右邊放1個。即:h(2)=h(0)*h(1)+h(1)*h(0)=2,則能組成2種形態的二叉樹。
當n=3時,1個根節點固定,還有2個節點。這2個節點可以分成(2,0),(1,1),(0,2)3組。即h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=5,則能組成5種形態的二叉樹。
以此類推,當n>=2時,可組成的二叉樹數量為h(n)=h(0)*h(n-1)+h(1)*h(n-2)+...+h(n-1)*h(0)種,即符合Catalan數的定義,可直接利用通項公式得出結果。
令h(1)=1,h(0)=1,catalan數(卡特蘭數)滿足遞歸式:
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=2)
另類遞歸式:
該遞推關系的解為:
h(n)=C(2n,n)/(n+1) (n=1,2,3,...)
由此想到了上次說的"N個數依次入棧,出棧順序有多少種?", 同樣用的也是卡特蘭數。
http://www.cnblogs.com/hujunzheng/p/4845354.html
代碼
class Solution { public: /** * @paramn n: An integer * @return: An integer */ long long C(int n, int m){ n = n-m+1; long long ans = 1; for(int i=1; i<=m; ++i){ ans *= n++; ans /= i; } return ans; } int numTrees(int n) { // write your code here return C(2*n, n)/(n+1); } };
構建不同形態二叉樹:
樣例
給出n = 3,生成所有5種不同形態的二叉查找樹:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
其實通過樣例,我們可以發現n個結點構造不同形態二叉樹的過程,1,2,3.....n個結點,枚舉每一個結點為根結點(假設為root, 1<=root<=n), 那么(1,2..root-1)和(root+1, root+2...n)分別是root的左右子樹。每一步不斷地重復上述過程,最終會得到所有形態的二叉樹。
算法實現
先弱弱的說一下自己錯誤的實現,因為遞歸實現的時候會得到不同的二叉樹,那么如何判斷n個結點正好生成了二叉樹呢?於是用了一個變量useNode(=0),表示當前已經用了多少個結點建樹。當useNode等於n的時候說明產生了一棵符合要求的樹,接着拷貝一下剛才生成的樹,然后放入vector中,繼續建造下一棵符合條件的二叉樹。
錯誤代碼:
/** * Definition of TreeNode: * class TreeNode { * public: * int val; * TreeNode *left, *right; * TreeNode(int val) { * this->val = val; * this->left = this->right = NULL; * } * } */ class Solution { public: /** * @paramn n: An integer * @return: A list of root */ vector<TreeNode *> ans; int cntNode=0;//節點的總數 TreeNode *curRoot = NULL; void copyT(TreeNode * &tmp, TreeNode *T){ if(T){ tmp = new TreeNode(T->val); copyT(tmp->left, T->left); copyT(tmp->right, T->right); } } void buildT(TreeNode * &T, int ld, int rd, int useNode){ if(ld > rd) return; for(int root=ld; root<=rd; ++root){ T = new TreeNode(root); if(ld==1 && rd==cntNode) curRoot = T; if(useNode+1==cntNode){//這個樹已經建立完畢,拷貝一下吧 TreeNode *tmp = NULL; copyT(tmp, curRoot); ans.push_back(tmp); } buildT(T->left, ld, root-1, useNode+1); buildT(T->right, root+1, rd, useNode+root-ld+1); } } vector<TreeNode *> generateTrees(int n) { // write your code here cntNode = n; TreeNode *T = NULL; buildT(T, 1, n, 0); if(n == 0) ans.push_back(T); return ans; } };
后來運行之后,看到錯誤的答案與正確答案的對比,如下:
當n=4的時候
輸出
[{1,#,2,#,3,#,4},{1,#,2,#,4,3},{1,#,3,2,4},{1,#,4,2,#,#,3},{1,#,4,3,#,2},{2,1,3,#,#,#,4},{2,1,4,#,#,3},{3,2,4,1},{4,1,#,#,2,#,3},{4,1,#,#,3,2},{4,2,#,1,3},{4,3,#,1,#,#,2},{4,3,#,2,#,1}]
期望答案
[{1,#,2,#,3,#,4},{1,#,2,#,4,3},{1,#,3,2,4},{1,#,4,2,#,#,3},{1,#,4,3,#,2},{2,1,3,#,#,#,4},{2,1,4,#,#,3},{3,1,4,#,2},{3,2,4,1},{4,1,#,#,2,#,3},{4,1,#,#,3,2},{4,2,#,1,3},{4,3,#,1,#,#,2},{4,3,#,2,#,1}]
也就是少了{3,1,4,#,2},以3為根結點的二叉樹為什么會少了呢?仔細想想,3結點的左孩子可以是1,也可以是2,那么左孩子為1的情況就被忽略了,此時useNode並不等於n,然后就換成左孩子為2結點的情況了。
正確代碼:
/** * Definition of TreeNode: * class TreeNode { * public: * int val; * TreeNode *left, *right; * TreeNode(int val) { * this->val = val; * this->left = this->right = NULL; * } * } */ class Solution { public: /** * @paramn n: An integer * @return: A list of root */ vector<TreeNode *> buildT(int ld, int rd){ vector<TreeNode *> ans; if(ld == rd) { TreeNode *T = new TreeNode(ld); ans.push_back(T); return ans; } if(ld > rd){ ans.push_back(NULL); return ans; } for(int i=ld; i<=rd; ++i){ vector<TreeNode *> ansLeft = buildT(ld, i-1); vector<TreeNode *> ansRight = buildT(i+1, rd); for(auto lx : ansLeft) for(auto rx : ansRight){ TreeNode *T = new TreeNode(i); T->left = lx; T->right = rx; ans.push_back(T); } } return ans; } vector<TreeNode *> generateTrees(int n) { // write your code here vector<TreeNode *> ans = buildT(1, n); return ans; } };
分析:在確定當前結點X后,那么X的左孩子結點(或右孩子結點)可能會有多個,那么就把這些可能的結點都存到vector中,然后從左孩子集合中任選出lx結點,以及從右孩子集合中選出rx結點,那么lx和rx就確定了一種形態的二叉樹。