1. 96.不同的二叉搜索树
原题链接
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例:
输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
1.1. 要点
- 掌握二叉搜索树
- 假定每一个数字为根结点,并将剩余的数组分配到左右两边继续构造子树。
此时每一个根结点构成二叉树的数量总和为左边*右边。
1.2. 递归
万变不离其宗
1.2.1. 代码片段
//96.不同的二叉搜索树
public int numTrees(int n) {
if (n == 0) {//1.终止条件,如果传入0或1直接可以返回结果了。
return n;
}
return numsTreesH(1, n);//2.计算[1...n]这个区间数字一共能够组成多少情况
}
public int numsTreesH(int start, int end) {
if (start >= end) {//3.请注意区间[1...n],所以[1...1]是可以参与运算,而[1...2]也就是越界了直接返回一种可能。又因为 [1...1]本身也只存在一种可能 所以这里 start>end 与 start>=end 是一样的 并且能够提前终止一次运算。
return 1;
}
int sum = 0;//4.初始化计算的总和
for (int i = start; i <= end; i++) {//6.分割 start 到 end 这个区间的数组:①[start...i1-1]、[i1+1...end],②[start...i2-1]、[i2+1...end]
int lNums = numsTreesH(start, i - 1);//7.计算左边的和
int rNums = numsTreesH(i + 1, end);//8.计算左边的和
sum += lNums * rNums;//将每一组 ①[start...i1-1]、[i1+1...end],②[start...i2-1]、[i2+1...end] 的结果相加得到最后的结果。
}
return sum;//5.返回结果
}
加入 HasMap 来直接将重复计算的结果返回
public int numTrees(int n) {
if (n == 0) {
return 0;
}
return numsTreesH(1, n, new HashMap<Integer, Integer>());
}
public int numsTreesH(int start, int end, HashMap<Integer, Integer> hashMap) {
if (start > end) {
return 1;
}
int length = end - start + 1;//1.[1..3]、[2..4]这种等距离的区间,计算的可能性是一样的,所以这里用它们之间的距离表示运算和。
Integer integer = hashMap.get(length);
if (integer != null) {//2.获取到结果直接返回
return integer;
}
int sum = 0;
for (int i = start; i <= end; i++) {
int lNums = numsTreesH(start, i - 1, hashMap);
int rNums = numsTreesH(i + 1, end, hashMap);
sum += lNums * rNums;
}
hashMap.put(length, sum);//3.更新这种区间距离的计算结果。
return sum;
}
1.2.2. 代码解释
- 右边有
n
中可能,此时可能性是 1 ∗ n 1*n 1∗n,如果左子树有m种就是 m ∗ n m*n m∗n 这个乘法的过程称为组合(即笛卡尔积)。
1.3. 动态规划
举例而言,
F
(
3
,
7
)
F(3, 7)
F(3,7),以 3
为根的不同二叉搜索树个数。为了以 3
为根从序列 [1, 2, 3, 4, 5, 6, 7] 构建二叉搜索树,我们需要从左子序列 [1, 2] 构建左子树,从右子序列 [4, 5, 6, 7] 构建右子树,然后将它们组合(即笛卡尔积)。
巧妙之处在于,我们可以将 [1,2] 构建不同左子树的数量表示为
G
(
2
)
G(2)
G(2) , 从 [4, 5, 6, 7] ` 构建不同右子树的数量表示为
G
(
4
)
G(4)
G(4)。这是由于
G
(
n
)
G(n)
G(n) 和序列的内容无关,只和序列的长度有关。于是,
F
(
3
,
7
)
=
G
(
2
)
⋅
G
(
4
)
F(3,7)=G(2)⋅G(4)
F(3,7)=G(2)⋅G(4)。 概括而言,我们可以得到以下公式:
假设现在的根节点是3:
F
(
3
,
7
)
=
G
(
2
)
⋅
G
(
4
)
(
1
)
F(3,7) =G(2)⋅G(4) \qquad(1)
F(3,7)=G(2)⋅G(4)(1)
F
(
i
,
n
)
=
G
(
i
−
1
)
⋅
G
(
n
−
i
)
(
2
)
F(i,n)=G(i−1)⋅G(n−i) \qquad (2)
F(i,n)=G(i−1)⋅G(n−i)(2)
G
(
n
)
=
∑
n
=
1
n
G
(
i
−
1
)
⋅
G
(
n
−
i
)
(
3
)
G(n)= \sum^n_{n=1}G(i−1)⋅G(n−i)\qquad (3)
G(n)=n=1∑nG(i−1)⋅G(n−i)(3)
1.3.2. 代码片段
public int numTrees2(int n) {
int[] G = new int[n + 1];//1.创建一个n+1容量的数组,额外存放一个0元素。
G[0] = 1;//2. 0,1都默认返回一种情况。
G[1] = 1;
for (int j = 2; j <= n; j++) {//3.计算 2之后的结果
for (int i = 1; i <= j; i++) {//4.计算公式
G[j] += G[i - 1] * G[j - i];
}
}
return G[n];
}
1.4. 数学推导“卡塔兰数”
移除了这部分内容,算法公式跟一般解法的思路不一样,对其它题目并不具有一般性暂不考虑。
95.不同的二叉搜索树II
原题链接
给定一个整数 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
要点
掌握二叉搜索树的构建过程,先切分数组,后创建根结点。
将有序数组转换为二叉搜索树
递归
利用递归过程中保存变量的性质,当整个调用过程结束的时候恰好是构造完树。
代码片段
func generateTreesHelper(start, end int) []*TreeNode {
all_trees := []*TreeNode{}
if start > end {
all_trees = append(all_trees, nil)
return all_trees
}
for i := start; i <= end; i++ {
left_trees := generateTreesHelper(start, i-1)
right_trees := generateTreesHelper(i+1, end)
for _, v_l := range left_trees {
for _, v_r := range right_trees {
node := &TreeNode{Val: i}
node.Left = v_l
node.Right = v_r
all_trees = append(all_trees, node)
}
}
}
return all_trees
}
func generateTrees(n int) []*TreeNode {
if n == 0 {
return nil
}
return generateTreesHelper(1, n)
}