leetCode-95-Unique-Binary-Search-TreesII
题目描述(中等难度)
给一个 n,用1…n 这些数字生成所有可能的二分查找树。所谓二分查找树,定义如下:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
解法一 回溯法
这是自己最早想到的一个思路。常规的回溯思想,就是普通的一个 for 循环,尝试插入 1, 2 … n,然后进入递归,在原来的基础上继续尝试插入 1, 2… n。直到树包含了所有的数字。就是差不多下边这样的框架。
递归{
递归出口;
for(int i = 1;i<=n;i++){
add(i);
进入递归;
remove(i);
}
}
看一下详细的代码。
public List<TreeNode> generateTrees(int n) {
List<TreeNode> ans = new ArrayList<TreeNode>();
if (n == 0) {
return ans;
}
TreeNode root = new TreeNode(0); //作为一个哨兵节点
getAns(n, ans, root, 0);
return ans;
}
private void getAns(int n, List<TreeNode> ans, TreeNode root, int count) {
if (count == n) {
//复制当前树并且加到结果中
TreeNode newRoot = treeCopy(root);
ans.add(newRoot.right);
return;
}
TreeNode root_copy = root;
//尝试插入每个数
for (int i = 1; i <= n; i++) {
root = root_copy;
//寻找要插入的位置
while (root != null) {
//在左子树中插入
if (i < root.val) {
//到了最左边
if (root.left==null) {
//插入当前数字
root.left = new TreeNode(i);
//进入递归
getAns(n, ans, root_copy, count + 1);
//还原为 null,尝试插入下个数字
root.left = null;
break;
}
root = root.left;
//在右子树中插入
} else if (i > root.val){
//到了最右边
if (root.right == null){
//插入当前数字
root.right = new TreeNode(i);
//进入递归
getAns(n, ans, root_copy, count + 1);
//还原为 null,尝试插入下个数字
root.right = null;
break;
}
root = root.right;
//如果找到了相等的数字,就结束,尝试下一个数字
} else {
break;
}
}
}
}
然而,理想很美丽,现实很骨感,出错了,就是回溯经常遇到的问题,出现了重复解。
//第一种情况
第一次循环添加 2
2
第二次循环添加 1
2
/
1
第三次循环添加 3
2
/ \
1 3
//第二种情况
第一次循环添加 2
2
第二次循环添加 3
2
\
3
第三次循环添加 1
2
/ \
1 3
是的,因为每次循环都尝试了所有数字,所以造成了重复。所以接下来就是解决避免重复数字的发生,然而经过种种努力,都失败了,所以这种思路就此结束,如果大家想出了避免重复的方法,欢迎和我交流。
解法二 递归
解法一完全没有用到查找二叉树的性质,暴力尝试了所有可能从而造成了重复。我们可以利用一下查找二叉树的性质。左子树的所有值小于根节点,右子树的所有值大于根节点。
所以如果求 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 ] 以及其他情况的所有可能,可以利用上边的方法,把每个数字作为根节点,然后把所有可能的左子树和右子树组合起来即可。
如果只有一个数字,那么所有可能就是一种情况,把该数字作为一棵树。而如果是 [ ],那就返回 null。
public List<TreeNode> generateTrees(int n) {
List<TreeNode> ans = new ArrayList<TreeNode>();
if (n == 0) {
return ans;
}
return getAns(1, n);
}
private List<TreeNode> getAns(int start, int end) {
List<TreeNode> ans = new ArrayList<TreeNode>();
//此时没有数字,将 null 加入结果中
if (start > end) {
ans.add(null);
return ans;
}
//只有一个数字,当前数字作为一棵树加入结果中
if (start == end) {
TreeNode tree = new TreeNode(start);
ans.add(tree);
return ans;
}
//尝试每个数字作为根节点
for (int i = start; i <= end; i++) {
//得到所有可能的左子树
List<TreeNode> leftTrees = getAns(start, i - 1);
//得到所有可能的右子树
List<TreeNode> rightTrees = getAns(i + 1, end);
//左子树右子树两两组合
for (TreeNode leftTree : leftTrees) {
for (TreeNode rightTree : rightTrees) {
TreeNode root = new TreeNode(i);
root.left = leftTree;
root.right = rightTree;
//加入到最终结果中
ans.add(root);
}
}
}
return ans;
}
解法三 动态规划
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com