LeetCode96. 不同的二叉搜索树
https://leetcode-cn.com/problems/unique-binary-search-trees/
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i] 表示1到i为节点组成的二叉搜索树的个数为dp[i]。
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
思考几种简单的情况:
- 当1为头结点的时候,其右子树有两个节点,布局和n 为2的时候两棵树的布局是一样的
- 当2位头结点的时候,其左右子树都只有一个节点,布局和n为1的时候只有一棵树的布局是一样的
- 当3为头结点的时候,其左子树有两个节点,布局和n 为2的时候两棵树的布局是一样的
从以上分析可以得出:
dp[3] = 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
元素1为头结点搜索树的数量 = 左子树有0个元素的搜索树数量 * 右子树有2个元素的搜索树数量
元素2为头结点搜索树的数量 = 左子树有1个元素的搜索树数量 * 右子树有1个元素的搜索树数量
元素3为头结点搜索树的数量 = 左子树有2个元素的搜索树数量 * 右子树有0个元素的搜索树数量
有0个元素的搜索树数量就是dp[0]。
有1个元素的搜索树数量就是dp[1]。
有2个元素的搜索树数量就是dp[2]。
所以dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0]
所以 dp[i] += dp[以j为头结点其左子树的数量] * dp[以j为头结点其右子树的数量],1 <= j <= i
所以:dp[i] += dp[j - 1] * dp[i - j],1 <= j <= i
3.思考初始状态
0个节点为空树: dp[0] = 1;
1个节点为仅有根节点的树:dp[1] = 1
4.自底向上计算得到最优解
for (int i = 2; i <= n; ++i)
{
for (int j = 1; j <= i; ++j)
{
dp[i] += dp[j - 1] * dp[i - j];
}
}
5.思考是否可以进行空间的优化
代码
class Solution
{
public:
int numTrees(int n)
{
vector<int> dp(n + 1, 0);
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; ++i)
{
for (int j = 1; j <= i; ++j)
{
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
};
LeetCode95. 不同的二叉搜索树II
https://leetcode-cn.com/problems/unique-binary-search-trees-ii/
给定一个整数 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
思路
基本思路:
要构建一颗二叉搜索树,只需要选择一个根结点,然后递归去构建其左右子树。
要构建多颗二叉树,就要按照一定的次序选择不同的根节点。
那么如何构建根结点的左右子树呢,我们抛开复杂的递归函数,只关心递归的返回值,每次选择一个根结点,
- 递归构建左子树,并拿到左子树所有可能的根结点列表
leftGroup
- 递归构建右子树,并拿到右子树所有可能的根结点列表
rightGroup
我们固定一个左孩子,遍历右子树列表,进行拼接,那么当前根结点的树个数就为leftGroup.size() * rightGroup.size()
个。
记忆化搜索优化:
使用"存储已生成的BST数组的二维数组"dp进行记忆化搜索。每次计算完[left, right]范围的BST数组就存储起来,下次搜索可以直接获取。如果dp[left] [right]不空,就表示存储过,直接返回。
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution
{
private:
//使用"存储已生成的BST数组的二维数组"进行记忆化搜索
vector<vector<vector<TreeNode*>>> dp;
vector<TreeNode*> generateTreesHelper(int left, int right)
{
if (left > right)
{
return {nullptr};
}
//如果有"已生成的BST数组",则直接返回
if (!dp[left][right].empty())
{
return dp[left][right];
}
vector<TreeNode*> result;
//分别以[left, right]为根节点
for (int i = left; i <= right; ++i)
{
//生成所有"以i为根节点的左子树"
vector<TreeNode*> leftGroup = generateTreesHelper(left, i - 1);
//生成所有"以i为根节点的右子树"
vector<TreeNode*> rightGroup = generateTreesHelper(i + 1, right);
//拼接
for (const auto& left : leftGroup)
{
for (const auto& right : rightGroup)
{
TreeNode* root = new TreeNode(i, left, right);
result.push_back(root);
}
}
}
//记忆"已生成的BST数组"
dp[left][right] = result;
return result;
}
public:
vector<TreeNode*> generateTrees(int n)
{
if (n == 0)
{
return {nullptr};
}
dp.resize(n + 1, vector<vector<TreeNode*>>(n + 1));
return generateTreesHelper(1, n);
}
};