题目
给定一个整数 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
提示:
0 <= n <= 8
分析
给定一个有序序列1…n,为了构建出一棵二叉搜索树,我们可以利用一下查找二叉树的性质。左子树的所有值小于根节点,右子树的所有值大于根节点。我们可以遍历每个数字i,将该数字作为树根,所以如果求1…n的所有可能。
我们只需要把1作为根节点, [] 空作为左子树, [2 … n]的所有可能作为右
子树。2作为根节点,[1] 作伪左子树, [3…n]的所有可能作为右子树。
3作为根节点,[1, 2 ]的所有可能作为左子树,[4 … n]的所有可能作为右子树,然后左子树和右子树两两组合。
4作为根节点[1 ,2,3]的所有可能作为佐子树,[5 … n]的所有可能作为右子树,然后佐子树和右子树两两组合。
…
n作为根节点,[ 1… n]的所有可能作为左子树,[]作为右子树。
由此可见,原问题可以分解成规模较小的两个子问题,子问题的解可以复用。
因此,我们可以想到使用递归、动态规划来求解本题。
解法一:递归搜索
按照以上分析的思路,利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。
如果只有一个数字,那么所有可能就是一种情况,把该数字作为一棵树。而如果是 [ ],那就返回 null。
public List<TreeNode> generateTrees(int n) {
if(n == 0) {
return new ArrayList<TreeNode>();
}
return dfs(1, n);
}
private List<TreeNode> dfs(int start, int end) {
// TODO Auto-generated method stub
List<TreeNode> res = new ArrayList<>();
//如果此时没有值,将null加入到结果中
if(start > end) {
res.add(null);
return res;
}
//如果只有一个数字,将当前数字作为一棵树加入到结果中
if(start == end) {
TreeNode tree = new TreeNode(start);
res.add(tree);
return res;
}
//尝试每个数字作为根节点
for(int i = start; i <= end; i++) {
//得到所有可能的左子树
List<TreeNode> leftTrees = dfs(start, i - 1);
//得到所有可能的右子树
List<TreeNode> rightTrees = dfs(i + 1, end);
//将所得的左子树右子树两两组合
for(TreeNode leftTree : leftTrees) {
for(TreeNode rightTree : rightTrees) {
TreeNode tree = new TreeNode(i);
tree.left = leftTree;
tree.right = rightTree;
//加入到结果集中去
res.add(tree);
}
}
}
return res;
}
解法二: 动态规划
仔细分析,可以发现一个规律。首先我们每次新增加的数字大于之前的所有数字,所以新增加的数字出现的位置只可能是根节点或者是根节点的右孩子,右孩子的右孩子,右孩子的右孩子的右孩子等等,总之一定是右边。其次,新数字所在位置原来的子树,改为当前插入数字的左孩子即可,因为插入数字是最大的。
由于求当前的所有解只需要上一次的解,所有我们只需要两个 list,pre 保存上一次的所有解, cur 计算当前的所有解。
public List<TreeNode> generateTrees2(int n) {
List<TreeNode> pre = new ArrayList<TreeNode>();
if (n == 0) {
return pre;
}
pre.add(null);
//每次增加一个数字
for (int i = 1; i <= n; i++) {
List<TreeNode> cur = new ArrayList<TreeNode>();
//遍历之前的所有解
for (TreeNode root : pre) {
//插入到根节点
TreeNode insert = new TreeNode(i);
insert.left = root;
cur.add(insert);
//插入到右孩子,右孩子的右孩子...最多找 n 次孩子
for (int j = 0; j <= n; j++) {
TreeNode root_copy = treeCopy(root); //复制当前的树
TreeNode right = root_copy; //找到要插入右孩子的位置
int k = 0;
//遍历 j 次找右孩子
for (; k < j; k++) {
if (right == null)
break;
right = right.right;
}
//到达 null 提前结束
if (right == null)
break;
//保存当前右孩子的位置的子树作为插入节点的左孩子
TreeNode rightTree = right.right;
insert = new TreeNode(i);
right.right = insert; //右孩子是插入的节点
insert.left = rightTree; //插入节点的左孩子更新为插入位置之前的子树
//加入结果中
cur.add(root_copy);
}
}
pre = cur;
}
return pre;
}
private TreeNode treeCopy(TreeNode root) {
if (root == null) {
return root;
}
TreeNode newRoot = new TreeNode(root.val);
newRoot.left = treeCopy(root.left);
newRoot.right = treeCopy(root.right);
return newRoot;
}