题目描述:给定一个整数 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
先说一下二叉搜索树的特点:
对于任意节点都满足:所有左子树的节点值小于根的值,所有右子树的节点值都大于根的值。
解题思路:
这个题一开始我的一个想法是使用深度优先搜索dfs(Depth First Search缩写)。
但开始的具体解题思路并不好实现于解这道题,因为我的做法是:利用dfs找出n的全排列集合,比如,3,就有123,132,213,231,312,321,然后是那个用这个序列进行搜索树的生成。
下面是dfs找n全排列的过程:
public static void findSolution(int n){
int book[][]= new int[n+1][n+1]; //标记数组
List<Integer> a = new ArrayList<>(); //记录一次排列顺序
int cur = 1; //表示当前位置
boolean hasNext = true; //是否还有下一个
while(true){
for(int k = 1 ; k<= n ;k++){
hasNext = false;
if(cur == n+1){
hasNext = false;
//这里可以加入:生成树并加入list的操作
for(int i = 0 ; i< a.size(); i++){
//打印找到的一个排列
System.out.print(" "+a.get(i));
}
System.out.println(" ");
break;
}
System.out.println("当前位置:"+cur+",k="+k+",book[cur][k]="+book[cur][k]+",a.contains(k)="+a.contains(k));
if(book[cur][k] !=1 && !a.contains(k)){
a.add(k);
book[cur][k] = 1;
cur++;
hasNext = true;
break;
}
}
if(!hasNext){
cur--;
if(cur == 0)
return;
a.remove(cur-1);
System.out.println("本次移除元素:"+cur);
if(a.isEmpty()){
System.out.println("length="+book.length);
for(int i = 2 ; i< book.length; i++){
book[i] = new int[n+1];
}
}
}
}
}
但这样做会有一个很严重的问题:比如3的全排列有6种可能,但生成的二叉搜索树只有5种,说明有重复。所以还需要去重复。
上面这种做法整个过程并不是十分清楚,当然是在有了一定比较你才会觉得。也就是下面这种做法,思路十分的清晰。
建议:在分析的过程中建议在纸上画一画。
思路:比如3,那我们就可以分别让1,2,3作为根节点,并以这个根节点作为分界线,在它左边的数就是它的左子树对应的节点集;在它右边的就是它的右子树对应的节点集。就会产生如下情况:
①root.val = 1:左子树集{},右子树集{2,3}
②root.val = 2:左子树集{1},右子树集{3}
③root.val = 3:左子树集{1,2},右子树集{}
然后你会观察到,为null的和只有一个元素的其实已经可以和root节点建立联系了,但是超过2个元素的集合还是需要去选择的。
所以我们可以把2个以上元素的再进行类似这样的划分:
把①中右子树集{2,3}:我们可以
让2为root,那么root的左子树集{},右子树集{3}
让3为root,那么root的左子树集{2},右子树{}
把③中左子树集{1,2}我们可以
让1为root,那么root的左子树集{},右子树集{2}
让2为root,那么root的左子树集{1},右子树集{}
这个时候所有的集合都被化为{}或者{一个元素}的情况了。
然后我们还可以吧{一个元素}这种集合也分解掉,这样就可以让结束程序的地方更加简洁,{一个元素}这种情况很好分解,直接让它当root然后左子树集{},右子树集也为{}。这样结束的条件就变成{}这种情况了。
下面看代码吧:
public List<TreeNode> generateTrees(int start,int end) {
List<TreeNode> list = new ArrayList<>();
//当start>end就表示子集没有元素了
if(start>end){
list.add(null);
}
//i从start到end就是分别让它们去做root,
//这里的root不是分得是最根部
//是指相对于left和right的那个root
for(int i = start; i<= end ; i++){
List<TreeNode> lefts = generateTrees(start,i-1);
List<TreeNode> rights = generateTrees(i+1,end);
for(TreeNode left : lefts){
for(TreeNode right : rights){
//从下面这句就可以看出上面三段注释
TreeNode root = new TreeNode(i);
root.left = left;
root.right = right;
list.add(root);
}
}
}
return list;
}
这个写法真的是很简洁呢!