二叉树之不同的二叉搜索树[Buffalo]
欢迎阅读、点赞、转发、订阅,你的举手之间,我的动力源泉。
1.不同的二叉搜索树
二叉搜索树
二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:
若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
若其右子树存在,则其右子树中每个节点的值都不小于该节点值。
定义状态
定义两个函数:
G(n)
:从1到n的连续整数形成的序列,组成不同的二叉搜索树的个数
F[i,n]
,以i
为root
节点,从1到n的连续整数形成的序列,组成不同的二叉搜索树的个数 ,其中1<=i<=n
转移方程
G(n)
可以选其中的某一个i
作为根节点的所有情况的和,即G(n)
=sum(F[i,n])
,其中1<=i<=n
F[i,n]
可以通过左右部分各自数量的乘积而得,如下图,可以得到F[i,n]
=G(i-1)
*G(n-i)
,注意G
函数的定义,再次参见上面的定义
有了上面的结论,G(n)
=sum(F[i,n])
,其中1<=i<=n就容易求得了
G(n)
=F[1,n]
+F[2,n]
+…+F[i,n]
+…+F[n-1,n]
+F[n,n]
等价于
G(n)
=G(1)
XG(n-1)
+G(2)
XG(n-2)
+…+G(i)
XG(n-i)
+…+G(n-1)
XG(0)
边界
G(0)
:表示一个元素都没有形成的二叉搜索树,只有一种情况,令其为1
G(1)
:表示只有个元素形成的二叉搜索树,只有一种情况,令其为1
完整代码
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
复杂度分析
时间复杂度:
O(N)
, 一次遍历即可,N
为整数的大小空间复杂度:
O(N)
, 数组dp
的大小
总结
很多牛叉的算法大多借助了一些辅助函数,如KMP
算法中的next
数组,本题也是如此,定义G(n)
和F[i,n]
,让问题很容易得到描述和分解
2.不同的二叉搜索树II
定义函数
helper(start,end)
表示从区间[start...end]
之间形成的二叉搜索树的列表
每次从[start...end]
选择一个数,为curr
节点,作为根节点,并递归生成左右两部分
左半部分,从
[start…i-1]
,生成所有可能的二叉搜索树列表右半部分,从
[i+1…end]
生成所有可能的二叉搜索树列表将左右部分组合到根节点的左右两侧并将结果收集到
result
列表,组合数可以参见上面一题的结果,就是size(leftPart)
Xsize(rightPart)
出口
当start
>end
s时,说明遇到了[]
或者一个节点的的情况,添加null
,后续追加到其所在的根节点
public List<TreeNode> generateTrees(int n) {
if (n == 0) return new ArrayList<>();
return helper(1, n);
}
private List<TreeNode> helper(int start, int end) {
List<TreeNode> result = new ArrayList<>();
if (start > end) {
result.add(null);
return result;
}
for (int i = start; i <= end; i++) {
List<TreeNode> leftPart = helper(start, i - 1);
List<TreeNode> rightPart = helper(i + 1, end);
for (TreeNode left : leftPart) {
for (TreeNode right : rightPart) {
TreeNode curr = new TreeNode(i);
curr.left = left;
curr.right = right;
result.add(curr);
}
}
}
// System.out.println(JSON.toJSONString(result));
return result;
}
动态规划做法
TODO