【问题描述】[中等]
【解答思路】
1. 递归
复杂度
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new LinkedList<TreeNode>();
}
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> allTrees = new LinkedList<TreeNode>();
if (start > end) {
allTrees.add(null);
return allTrees;
}
// 枚举可行根节点
for (int i = start; i <= end; i++) {
// 获得所有可行的左子树集合
List<TreeNode> leftTrees = generateTrees(start, i - 1);
// 获得所有可行的右子树集合
List<TreeNode> rightTrees = generateTrees(i + 1, end);
// 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
TreeNode currTree = new TreeNode(i);
currTree.left = left;
currTree.right = right;
allTrees.add(currTree);
}
}
}
return allTrees;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/unique-binary-search-trees-ii/solution/bu-tong-de-er-cha-sou-suo-shu-ii-by-leetcode-solut/
2. 递归 从构建单棵树到构建所有树
构建一颗二叉搜索树
构建一颗二叉搜索树很简单,只需要选择一个根结点,然后递归去构建其左右子树。
public TreeNode createBinaryTree(int n){
return helper(1, n);
}
public TreeNode helper(int start, int end){
if(start > end)
return null;
// 这里可以选择从start到end的任何一个值做为根结点,
// 这里选择它们的中点,实际上,这样构建出来的是一颗平衡二叉搜索树
int val = (start + end) / 2;
TreeNode root = new TreeNode(val);
root.left = helper(start, val - 1);
root.right = helper(val + 1, end);
return root;
}
要构建多颗二叉树,问题就在于如何选择不同的根节点,以构建不同的树和子树。
在上面的代码中,在选择根结点的时候,可以这样改造
// 选择所有可能的根结点
for(int i = start; i <= end; i++){
TreeNode root = new TreeNode(i);
...
}
但是如果按照上述递归函数的方法写,每次递归只能返回一颗树,我们需要的是多颗树,我们可以将不同的根结点装入List然后返回,实际上,上述代码可以改写成
public List<TreeNode> helper(int start, int end){
List<TreeNode> list = new ArrayList<>();
if(start > end){
list.add(null);
return list;
}
for(int i = start; i <= end; i++){
TreeNode root = new TreeNode(i);
...
...
// 装入所有根结点
list.add(root);
}
return list;
}
很显然,现在问题变成了如何构建root的左右子树,我们抛开复杂的递归函数,只关心递归的返回值,每次选择根结点root,我们
- 递归构建左子树,并拿到左子树所有可能的根结点列表left
- 递归构建右子树,并拿到右子树所有可能的根结点列表right
这个时候我们有了左右子树列表,我们的左右子树都是各不相同的,因为根结点不同,我们如何通过左右子树列表构建出所有的以root为根的树呢?
我们固定一个左孩子,遍历右子树列表,那么以当前为root根结点的树个数就为**left.size() * right.size()**个。
class Solution {
public List<TreeNode> generateTrees(int n) {
if(n < 1)
return new ArrayList<>();
return helper(1, n);
}
public List<TreeNode> helper(int start, int end){
List<TreeNode> list = new ArrayList<>();
if(start > end){
// 如果当前子树为空,不加null行吗?
list.add(null);
return list;
}
for(int i = start; i <= end; i++){
// 想想为什么这行不能放在这里,而放在下面?
// TreeNode root = new TreeNode(i);
List<TreeNode> left = helper(start, i-1);
List<TreeNode> right = helper(i+1, end);
// 固定左孩子,遍历右孩子
for(TreeNode l : left){
for(TreeNode r : right){
TreeNode root = new TreeNode(i);
root.left = l;
root.right = r;
list.add(root);
}
}
}
return list;
}
}
关于**TreeNode root = new TreeNode(i)**的放置的位置问题
如果这行代码放置在注释的地方,会造成一个问题,就是以当前为root根结点的树个数就
num = left.size() * right.size() > 1时,num棵子树会共用这个root结点,在下面两层for循环中,root的左右子树一直在更新,如果每次不新建一个root,就会导致num个root为根节点的树都相同。
关于如果当前子树为空,不加null行不行的问题
显然,如果一颗树的左子树为空,右子树不为空,要正确构建所有树,依赖于对左右子树列表的遍历,也就是上述代码两层for循环的地方,如果其中一个列表为空,那么循环都将无法进行。
【总结】
1. 从构建单棵树到构建所有树,清晰易懂的递归思路。 由简单到复杂 思路二循序渐进,值得学习
2.树的问题要善用递归、回溯(需要记忆化才不容易超时)
转载链接:https://leetcode-cn.com/problems/unique-binary-search-trees-ii/solution/cong-gou-jian-dan-ke-shu-dao-gou-jian-suo-you-shu-/