一 LC95.不同的二叉搜索树II
题目要求:
给你一个整数
n
,请你生成并返回所有由n
个节点组成且节点值从1
到n
互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
思路分析:
要生成所有由 n
个节点组成的、且节点值从 1
到 n
互不相同的不同二叉搜索树 (BST),可以使用递归和动态规划相结合的方式。对于每个数字 i
(从 1
到 n
),我们可以将 i
作为根节点,递归生成以 1
到 i-1
为节点的左子树集合,以及以 i+1
到 n
为节点的右子树集合,然后将这些左子树和右子树进行组合,形成以 i
为根节点的所有可能的二叉搜索树。当 start > end
时,返回一个 null
节点表示没有子树(空树)。
对于每个根节点 i
,从左子树集合和右子树集合中取出一个左子树和一个右子树,组合成一棵新树,并将其加入结果集中。
完整代码示例:
import java.util.ArrayList;
import java.util.List;
// 定义二叉树节点类
class TreeNode {
int val; // 节点值
TreeNode left; // 左子树
TreeNode right; // 右子树
// 构造函数,初始化节点值
TreeNode(int x) {
val = x;
}
}
public class Solution {
// 生成所有由 n 个节点组成的不同二叉搜索树
public List<TreeNode> generateTrees(int n) {
// 如果 n 为 0,则返回一个空的列表
if (n == 0) {
return new ArrayList<>();
}
// 调用递归方法生成从 1 到 n 的所有二叉搜索树
return generateTrees(1, n);
}
// 递归方法:生成从 start 到 end 范围内所有可能的二叉搜索树
private List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> allTrees = new ArrayList<>(); // 存储所有可能的树
// 递归的基准情况:如果 start > end,则返回包含 null 的列表,表示没有子树
if (start > end) {
allTrees.add(null);
return allTrees;
}
// 遍历范围内的每个数字 i,将其作为当前树的根节点
for (int i = start; i <= end; i++) {
// 递归生成所有可能的左子树,左子树的节点范围是 start 到 i-1
List<TreeNode> leftTrees = generateTrees(start, i - 1);
// 递归生成所有可能的右子树,右子树的节点范围是 i+1 到 end
List<TreeNode> rightTrees = generateTrees(i + 1, end);
// 将每个左子树和右子树组合到当前根节点 i
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
// 创建一个新的树节点,将 i 作为根节点
TreeNode currentTree = new TreeNode(i);
currentTree.left = left; // 连接左子树
currentTree.right = right; // 连接右子树
allTrees.add(currentTree); // 将生成的树加入结果列表
}
}
}
// 返回当前范围内所有可能的二叉搜索树
return allTrees;
}
}
二 LC96.不同的二叉搜索树
题目要求:
给你一个整数
n
,求恰由n
个节点组成且节点值从1
到n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
思路分析:
这是一个典型的动态规划问题,可以使用卡特兰数(Catalan Number)来求解。设 G(n)
表示有 n
个节点的二叉搜索树的总数,对于每个 i
(1 <= i <= n
),我们可以将 i
作为根节点。左子树的节点个数为 i-1
,即 G(i-1)
。右子树的节点个数为 n-i
,即 G(n-i)
。因此,可以得到状态转移方程: G(n)=∑i=1nG(i−1)×G(n−i)G(n) = \sum_{i=1}^{n} G(i-1) \times G(n-i)G(n)=i=1∑nG(i−1)×G(n−i)。这里我们需要去判断一些特殊情况,也就是当没有节点时,只有一种(空树)的情况(G(0) = 1
)或者当只有一个节点时的情况(G(1) = 1
)。
完整代码示例:
public class Solution {
// 计算由 n 个节点组成的不同二叉搜索树的数量
public int numTrees(int n) {
// 动态规划数组,用于存储不同节点数的BST数量
int[] dp = new int[n + 1];
// 基础条件
dp[0] = 1; // 空树的情况
dp[1] = 1; // 只有一个节点的情况
// 从2开始计算,直到n
for (int i = 2; i <= n; i++) {
// 对于每个i,计算不同根节点的所有情况
for (int j = 1; j <= i; j++) {
// dp[i] += 左子树的BST数量 * 右子树的BST数量
dp[i] += dp[j - 1] * dp[i - j];
}
}
// 返回结果,即由n个节点组成的BST的数量
return dp[n];
}
}
三 LC98.验证二叉搜索树
题目要求:
给你一个二叉树的根节点
root
,判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
思路分析:
要判断一个二叉树是否是有效的二叉搜索树,我们可以使用中序遍历的思想。对于二叉搜索树,按中序遍历顺序得到的节点值序列应该是严格递增的。因此,通过中序遍历来检查节点值是否递增,可以判断该二叉树是否是有效的二叉搜索树。
使用递归来遍历每个节点,同时传递当前节点的上下界(最小值和最大值),确保当前节点的值在这些上下界之间。对于每个节点而言,如果当前节点的值不在给定的上下界之间,则该树不是二叉搜索树。然后递归检查左子树和右子树,对左子树递归时,更新上界为当前节点值,对右子树递归时,更新下界为当前节点值。
完整代码示例:
class TreeNode {
int val; // 节点值
TreeNode left; // 左子树
TreeNode right; // 右子树
// 构造函数,初始化节点值
TreeNode(int x) {
val = x;
}
}
public class Solution {
// 判断给定的二叉树是否是一个有效的二叉搜索树
public boolean isValidBST(TreeNode root) {
// 调用递归方法,初始上下界设为Long的最小值和最大值
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
// 递归方法,判断以当前节点为根的子树是否满足BST条件
private boolean isValidBST(TreeNode node, long min, long max) {
// 空节点直接返回true
if (node == null) {
return true;
}
// 如果当前节点值不在合法范围内,则返回false
if (node.val <= min || node.val >= max) {
return false;
}
// 递归检查左子树,更新上界为当前节点值
// 递归检查右子树,更新下界为当前节点值
return isValidBST(node.left, min, node.val) && isValidBST(node.right, node.val, max);
}
}