参考链接:https://blog.csdn.net/happyaaaaaaaaaaa/article/details/51635367
在参考原本的基础上,使用一个辅助数组,保存递归中的值,避免重复计算。
//递归实现
public List<TreeNode> generateTrees(int n) {
if (n < 1) return new ArrayList<>();
//利用一个辅助数组保存中间的值,避免重复求取,但是也会导致有些子树的结点是公共的
List[][] dp = new List[n + 2][n + 2];
return generateTrees(1, n,dp);
}
private List<TreeNode> generateTrees(int start, int end, List[][] dp) {
List<TreeNode> res = new ArrayList<>();
if (end < start) {
res.add(null);
return res;
}
for (int i = start; i <= end; i++) {
List<TreeNode> list1 = dp[start][i - 1];
if (list1 == null) {
//递归,并将结果保存下来
list1 = generateTrees(start, i - 1, dp);
dp[start][i - 1] = list1;
}
//(start,i-1)为左子树,遍历不同的左子树组合
for (TreeNode left : list1) {
List<TreeNode> list2 = dp[i + 1][end];//当 i = n 时,就需要求 dp[n+1][end] 的值
if (list2 == null) {
//递归,并将结果保存下来
list2 = generateTrees(i + 1, end, dp);
dp[i + 1][end] = list2;
}
//(i+1,end)为右子树,遍历不同的右子树组合
for (TreeNode right : list2) {
TreeNode root = new TreeNode(i);
root.left = left;
root.right = right;
res.add(root);
}
}
}
return res;
}
主要就是利用分治的思想(可以参考 【96. 不同的二叉搜索树】),以 [start,end]
中的某一个为根结点 i
,然后划分左右子树 [start,i-1]
(左子树) 和 [i+1,end]
(右子树),分别遍历两边子树的不同组合结果,并进行拼合。
因为用到了一个辅助的 List<TreeNode>
数组保存递归的值,所以可以避免一些重复的递归,但是也会因重复利用保存的值,导致一些子树的结点是公共的。如下:
(打印 n = 3 时的结果,前面的数字为 TreeNode 的哈希值,括号里的数组为 TreeNode 的 val 值)
如上红线划记的两部分,红线上的表示包括的,红线下的表示排除的元素(因为是按树的层次遍历打印的),这两部分就是重复的。
如果不用辅助数组保存,则每次递归都是生成新的 List<TreeNode>
,此时就不会出现子树的结点是公共的情况。如下: