LeetCode 95&96 不同的二叉搜索树

1,362 阅读4分钟

95题. 构建所有可能的二叉搜索树

1. 题目描述

给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。
示例

输入:3
输出以下5种二叉搜索树的根节点的指针:

  1         3     3      2      1  
   \       /     /      / \      \  
    3     2     1      1   3      2  
   /     /       \                 \  
  2     1         2                 3

2. 题解思路

对于所有的构建二叉树的题目首先应该想到用递归的方法来构建二叉树。而这一题给出节点的个数需要构建出所有可能的二叉树来,那么显然就涉及到一个遍历的问题。首先每一个节点都有是根节点的可能,当我们确定用第k(1 <= k <= n)个节点来作为根节点时,其前面有k-1个节点( 1 ... k - 1 ),这些节点由于都比我们选出的节点小,因此都应在根节点的左树里。换句话说, 1 ... k - 1会构成根节点的左子树,这显然是一个相同的子问题。同理( k + 1 ... n )所构成的右子树也是一个相同的子问题。假设左子树有x种可能,右子树有y种可能,那么根节点将所有的情况都组合起来就有x * y种可能。
因此整个过程就不理解:首先需要遍历所有节点是根节点的情况;对每一种情况都需要递归的构建其左子树和右子树;遍历左子树和右子树的每一种可能的组合,将其和根节点连接起来;返回根节点。
代码如下:

class Solution {
public:
	vector<TreeNode*> generateTrees(int n) {
		vector<TreeNode*> result;
		if (n == 0)
			return result;
		result = generateCore(1, n);
		return result;
	}

	vector<TreeNode*> generateCore(int start, int end) {
		vector<TreeNode*> result;
		//边界条件
		if (start > end) {
			result.push_back(NULL);
			return result;
		}
		vector<TreeNode*> leftTree;
		vector<TreeNode*> rightTree;
		for (int index = start; index <= end; index++) {
			//递归得到左右子树的所有可能
			leftTree = generateCore(start, index - 1);
			rightTree = generateCore(index + 1, end);

			//把左右子树的每一种可能都组合起来
			int lsize = leftTree.size();
			int rsize = rightTree.size();
			for (int i = 0; i < lsize; i++) {
				for (int j = 0; j < rsize; j++) {
					TreeNode* root = new TreeNode(index);
					root->left = leftTree[i];
					root->right = rightTree[j];
					result.push_back(root);
				}
			}
		}
		return result;
	}
};

3. 代码中需要注意的细节

首先需要注意的是对左子树和右子树递归也是有多种可能的,所以返回值是根节点的vector。
另外有一个bug我找了好久才找到,就是在将左右子树相连的时候,new根节点的操作没有写在循环里。这就导致,对于每一种组合的根节点,我都用了同一个指针来指向。这就导致接下来组合都没有新建一个树,而是在之前的组合上修改。这个bug确实难查,因为我debug的时候更多地关注了代码的执行逻辑,很容易忽略这里。写代码的时候还是要尽量小心。

96题. 计算可以构建出来所有树的个数

显然,这一题在上一题的基础上是很简单的。都不需要构建,只需要计算树的可能性的个数就可以了。思路还是一样的:遍历每个节点为根节点的情况;递归计算其左子树的个数x,右子树的个数y;组合起来的个数,就是x * y。仿照95题不难写出递归的解法。
代码如下:

class Solution {
public:
    int numTrees(int n) {
        if(n <= 0)
            return 0;
        int result = numTrees(1, n);
        return result;
    }
    
    int numTrees(int start, int end){
        if(start >= end){
            return 1;
        }
        int result = 0;
        for(int index = start; index <= end; index++){
            int numLeftTrees = numTrees(start, index - 1);
            int numRightTrees = numTrees(index + 1, end);
            result += numLeftTrees * numRightTrees;
        }
        return result;
    }
};

上述递归形式的代码,写完后测试了下n=3的情况,输出正确。由于逻辑和95题一致,心想这不可能会错就提交了。但是却提示输入为19的时候超时了。很自然地就想到应该是递归的问题,因为会同时递归左子树和右子树,就像fabnocci数列一样,项数太多的时候就会调用太多。想到这里问题也很好解决,只需要向fabnocci数列一样采用迭代的方法代替递归即可。但是由于计算下一项不是只根据前面两项的计算结果,所以相比于fabnocci来说,这一题需要把前面所有项的计算结果存下来,因此需要用一个一维数组来存,而不是两个变量。
代码如下:

 class Solution {
 public:
	 int numTrees(int n) {
		 if (n <= 0)
			 return 0;
		 vector<int> dp(n + 1, 0);
		 dp[0] = 1;
		 dp[1] = 1;
		 for (int end = 2; end <= n; end++) {
			 for (int i = 1; i <= end; i++) {
				 dp[end] += dp[i - 1] * dp[end - i];
			 }
		 }
		 return dp[n];
	 }
 };