[LeetCode] 95. Unique Binary Search Trees II 独一无二的二叉搜索树之二
Category | Difficulty | Likes | Dislikes |
---|---|---|---|
algorithms | Medium (62.81%) | 407 | - |
TagsCompanies
给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树 。
示例:输入:3
输出:
[ [1,null,3,2], [3,2,null,1], [3,1,null,null,2], [2,1,3], [1,null,2,null,3] ] 解释: 以上的输出对应以下 5 种不同结构的二叉搜索树: 1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2
3
这道题是之前的
96 Unique Binary Search Trees 的延伸,之前那个只要求算出所有不同的二叉搜索树的个数,这道题让把那些二叉树都建立出来。
解法一:递归
这种建树问题一般来说都是用递归来解,这道题也不例外,划分左右子树,递归构造。
查找二叉树的性质:左子树的所有值小于根节点,右子树的所有值大于根节点。
所以如果求 1...n 的所有可能。
我们只需要把 1 作为根节点,[ ] 空作为左子树,[ 2 ... n ] 的所有可能作为右子树。
2 作为根节点,[ 1 ] 作为左子树,[ 3...n ] 的所有可能作为右子树。
3 作为根节点,[ 1 2 ] 的所有可能作为左子树,[ 4 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
4 作为根节点,[ 1 2 3 ] 的所有可能作为左子树,[ 5 ... n ] 的所有可能作为右子树,然后左子树和右子树两两组合。
...
n 作为根节点,[ 1... n ] 的所有可能作为左子树,[ ] 作为右子树。
至于,[ 2 ... n ] 的所有可能、 [ 4 ... n ] 以及其他情况的所有可能,可以利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。
如果只有一个数字,那么所有可能就是一种情况,把该数字作为一棵树。而如果是 0,那就返回 null。
1 class Solution {
2 public:
3 vector<TreeNode*> generateTrees(int n)
4 {
5 vector<TreeNode*> ans;
6 if(n==0) return ans;
7 return getAns(1,n);
8 }
9 private:
10 vector<TreeNode*> getAns(int start,int end)
11 {
12 vector<TreeNode*> ans;
13 if(start>end)
14 {
15 ans.push_back(nullptr);
16 }
17 else if(start == end)
18 {
19 TreeNode* tree = new TreeNode(start);
20 ans.push_back(tree);
21 }
22 else
23 {
24 for(int i=start; i<=end; ++i)//依次把每个数字作为根
25 {
26 vector<TreeNode*> leftTrees = getAns(start,i-1);//递归求i左子树的结果
27 vector<TreeNode*> rightTrees = getAns(i+1,end);////递归求i右子树的结果
28 //把i的所有可能的左子树和所有可能的右子树组合起来
29 for(int l = 0;l<leftTrees.size();++l)
30 for(int r = 0;r<rightTrees.size();++r)
31 {
32 TreeNode* root = new TreeNode(i);
33 root->left = leftTrees[l];
34 root->right = rightTrees[r];
35 ans.push_back(root);
36 }
37 }
38 }
39 return ans;
40 }
41 };
解法二:动态规划
考虑 [] 的所有解
null
考虑 [ 1 ] 的所有解
1
考虑 [ 1 2 ] 的所有解
2
/
1
1
\
2
考虑 [ 1 2 3 ] 的所有解
3
/
2
/
1
2
/ \
1 3
3
/
1
\
2
1
\
3
/
2
1
\
2
\
3
仔细分析,可以发现一个规律。首先我们每次新增加的数字大于之前的所有数字,所以新增加的数字出现的位置只可能是根节点或者是根节点的右孩子,
右孩子的右孩子,右孩子的右孩子的右孩子等等,总之一定是右边。其次,新数字所在位置原来的整个子树,改为当前插入数字的左孩子即可,因为插入数字是最大的。
比如对下面的1,2,3,4组成的二叉搜索树增加节点5;
1
\
3
/ \
2 4
第一步:作为根结点,原来的整棵树作为5的左子树
5
/
1
\
3
/ \
2 4
第二步:作为1的右节点,1的原本的右子树作为5的左子树:
1
\
5
/
3
/ \
2 4
第三步:作为3的右节点,3的原本的右子树作为5的左子树:
1
\
3
/ \
2 5
/
4
第四步:作为4的右节点,的原本的右子树(null)作为5的左子树:
1
\
3
/ \
2 4
\
5
动态规划实现的代码如下:
1 /* 2 * @Descripttion: 3 * @version: 4 * @Author: wangxf 5 * @Date: 2020-06-07 00:03:19 6 * @LastEditors: Do not edit 7 * @LastEditTime: 2020-06-09 01:53:31 8 */ 9 /* 10 * @lc app=leetcode.cn id=95 lang=cpp 11 * 12 * [95] 不同的二叉搜索树 II 13 */ 14 15 // @lc code=start 16 /** 17 * Definition for a binary tree node. 18 * struct TreeNode { 19 * int val; 20 * TreeNode *left; 21 * TreeNode *right; 22 * TreeNode() : val(0), left(nullptr), right(nullptr) {} 23 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 24 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 25 * }; 26 */ 27 28 class Solution { 29 public: 30 vector<TreeNode*> generateTrees(int n) 31 { 32 vector<TreeNode*> pre; 33 if(n==0) 34 { 35 return pre; 36 } 37 pre.push_back(nullptr); 38 for(int i=1;i<=n;++i) 39 { 40 vector<TreeNode*> cur; 41 for(auto root:pre) 42 { 43 //新节点作为根插入 44 TreeNode* insert = new TreeNode(i); 45 insert->left = root; 46 cur.push_back(insert); 47 //新节点作为根的右节点,右节点的右节点,右节点的右节点的右节点...插入 48 for(int j=0;j<n;++j)//最多有i-1个右节点 49 { 50 TreeNode* new_root = copytree(root);//拷贝生成新的待插入树 51 TreeNode* right = new_root;//工作指针 52 int k = 0; 53 for(;k<j;++k) 54 { 55 if(right==nullptr) break; 56 right = right->right; 57 } 58 if(right==nullptr){ 59 break; 60 } 61 insert = new TreeNode(i); 62 insert->left = right->right; 63 right->right = insert; 64 cur.push_back(new_root); 65 } 66 } 67 pre = cur; 68 } 69 return pre; 70 } 71 private: 72 //拷贝生成一棵树 73 TreeNode* copytree(TreeNode* root) 74 { 75 if(!root) 76 { 77 return nullptr; 78 } 79 TreeNode* new_root = new TreeNode(root->val); 80 new_root->left = copytree(root->left); 81 new_root->right = copytree(root->right); 82 return new_root; 83 } 84 85 }; 86 // @lc code=end
注:代码中存在内存泄漏的风险,不宜用在生产环境中。