题目
给定一个整数 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] ]
动态规划
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Main main = new Main();
main.generateTrees(4);
}
public List<TreeNode> generateTrees(int n) {
Map<Integer, List<TreeNode>> dpTable = new HashMap<>();
for (int i = 0; i < n; i ++) {
List<TreeNode> tempList = new ArrayList<>();
if (i == 0) {
TreeNode root = new TreeNode(1);
tempList.add(root);
}
if (i == 1) {
TreeNode root1 = new TreeNode(2);
root1.left = new TreeNode(1);
TreeNode root2 = new TreeNode(1);
root2.right = new TreeNode(2);
tempList.add(root1);
tempList.add(root2);
}
if (i >= 2) {
for (int j = 0; j <= i; j ++) {
// 右边二叉树可能的结构
List<TreeNode> rightList = new ArrayList<>();
if (j < i) {
// 当j右边有二叉树的时候
rightList = dpTable.get(i - j - 1);
// 刷新右边二叉树结构里的所有值
List<TreeNode> tempRightList = new ArrayList<>();
for (TreeNode rightRoot : rightList) {
tempRightList.add(cloneTreeNode(rightRoot, j + 1));
}
rightList = tempRightList;
} else {
rightList.add(null);
}
// 左边二叉树可能的结构
List<TreeNode> leftList = new ArrayList<>();
if (j > 0) {
// 当j左边有二叉树的结构时 左边的值不用刷新
leftList = dpTable.get(j - 1);
} else {
leftList.add(null);
}
// 组合左右二叉树
// 以右边为基准
for (TreeNode rightNode : rightList) {
for (TreeNode leftNode : leftList) {
TreeNode root = new TreeNode(j + 1);
root.right = rightNode;
root.left = leftNode;
tempList.add(root);
}
}
}
}
dpTable.put(i, tempList);
}
return dpTable.get(n - 1);
}
// 对于相同结构, 但是节点value值不同的树重新赋值, 生成新的节点
public TreeNode cloneTreeNode(TreeNode oldTreeNode, int value) {
// 构造一个新的TreeNode, 在oldTreeNode的基础上, 有值的部分全部加value
if (oldTreeNode == null) return null;
TreeNode newTree = new TreeNode(value + oldTreeNode.val);
newTree.left = cloneTreeNode(oldTreeNode.left, value);
newTree.right = cloneTreeNode(oldTreeNode.right, value);
return newTree;
}
}
基本思路
-
延续二叉搜索数1的思路, 通过假定root节点为1...n的任何一个值的时候, 将问题分解为求左边子树的结果和右边子树的结果.
-
不同的是, 需要给出所有可能的方案, 而不是可能的种数, 那么就有个问题, 就是一个root分割开的左右子树, 虽然能变成子问题, 但是右子树的情况, 只和子问题的树的结构相同, 但是数字不同, 因此要想办法在同样的结构上赋予不同的数字(经过观察发现需要将右子树上的每个节点的值加上当前root的值即可, 需要遍历右子树), 左子树的数值是正确的无需修改
-
需要修改dpTbale的类型, 需要一个map, 其它的思路是一致的
-
左右子树组合的时候, 如果左边或者右边为空, 添加个null, 就可以解决选哪边遍历的基准问题(不然要讨论左为空, 右为空, 左右都不为空的情况)
优点
-
继续沿用了动态规划的思想, 并且能得到正确的答案, 只需要将问题进一步升级即可(关键是发现相同结构, 不同值的二叉树是如何生成的)
-
需要知道怎么遍历一个二叉树, 例如先序遍历等
缺点
- 同构不同值的二叉树在构造时 比较耗费时间, 效率不是很高
递归
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Main main = new Main();
main.generateTrees(3);
}
public List<TreeNode> generateTrees(int n) {
List<TreeNode> result = digui(1, n);
return result;
}
public List<TreeNode> digui(int start, int end) {
List<TreeNode> tempList = new ArrayList<>();
if (start > end) {
tempList.add(null);
return tempList;
}
for (int i = start; i <= end; i ++) {
// 将左边一串数字去生成多个子树
List<TreeNode> leftList = digui(start, i - 1);
// 将右边的一串数字生成子树
List<TreeNode> rightList = digui(i + 1, end);
// 组合左右子树 此时左右子树list里一定有值,虽然可能是null
for (TreeNode leftNode : leftList) {
for (TreeNode rightNdoe : rightList) {
// 选择根节点
TreeNode root = new TreeNode(i);
root.left = leftNode;
root.right = rightNdoe;
tempList.add(root);
}
}
}
return tempList;
}
}
基本思路
-
思路会转变成自顶向下
-
将题目简化成如果给你1...n个数, 如何构建一个平衡二叉树, 即选取 n/2 作为root节点, 然后左边的一串数字即1... n/2 - 1, 去构造一个新的平衡二叉树作为root的左节点, 然后右边的一串数字, n / 2 + 1 ....n去构建去构造一个新的平衡二叉树作为root的右节点.
-
如果不是一颗平衡二叉树呢, 那么我们在选择当前root值的时候, 就不再是n/2了, 而是遍历1...n, 其它的思路不变.
-
每次递归返回的多个节点的时候, 要注意