Leetcode 96 Unique Binary Search Trees

本文详细阐述了如何通过动态规划优化算法解决LeetCode中的独特二叉搜索树问题,包括代码实现、运行情况分析及优化策略。重点在于通过空间换时间的方法减少内存消耗和避免重复计算,最终实现高效解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题地址:https://leetcode.com/problems/unique-binary-search-trees/

题目描述

Given n, how many structurally unique BST’s (binary search trees) that store values 1…n?
给出n,返回结构上不重复的二叉查找树的个数(每个二叉树都存储1,2,3,…,n)。

For example,
举例,

Given n = 3, there are a total of 5 unique BST’s.
给出n=3,共有5种各异的二叉查找树:

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

对于 n=3,返回 5

解题思路

这是一个动态规划问题。

由于我是先做的Leetcode 95(博客记录:Leetcode 95 Unique Binary Search Trees II),因此很自然得认为想到在Leetcode 95的基础上对程序进行修改即可轻松得到正确的代码。然而仅仅修改代码遇到了以下问题:

1. Memory Limit Exceeded 超过内存限制
2. Time Limit Exceeded 超过时间限制

为了解决上述方法,我主要采取以下方法来优化代码:

1. 特别注意对于内存的管理,对于已经不再使用的内存及时进行释放。
2. 有些情况下是无须重复计算的,如果重复计算的话就会有超时的可能。

对于第一点,这个没什么技巧, 对于无用的内存释放即可。

对于第二点,我们可以发现如下规律,对于两个不存在重复元素的数组arr1和arr2,当他们元素数量相同时,就算他们存储的元素不全都一样,他们可以构成的二叉查找树是一样多的。因此我们考虑使用一个数组numsTrees[n+1]来存储可以构成的异构二叉查找树的数目,这样只需要计算n+1次,其他情况下直接使用已经存储的结果即可。这是一种以空间换时间的方法。空间复杂度O(n),时间复杂度O(n)。

代码

/**
 * 返回由n个数字可以构成的异构二叉查找树的数目
 * Input n : 最大值(即1-n共n个数)
 * Return : 异构的二叉查找树的数目
 */
int numTrees(int n) {
    int numTreesFromArray(int *, int *, int);

    // 如果n为0或1,直接返回1
    if (n == 0 || n == 1)
        return 1;

    // 分配备选数组nums内存
    int *nums = (int *)malloc(sizeof(int) * n);
    // 分配numsTrees内存,numsTrees[i]代表用i个数可以构成的异构二叉查找树的数目
    int *numsTrees = (int *)malloc(sizeof(int) * (n + 1));
    // 初始化
    int i;
    for (i = 0; i < n; ++i) {
        *(nums + i) = i + 1;  // 初始化备选数组nums,nums中存储着1-n(有序)
        *(numsTrees + i) = 0; // 初始化结果集numsTrees = {0}
    }
    *(numsTrees + n) = 0; // 初始化numsTrees[n] = 0
    *numsTrees = 1;       // 初始化numsTrees[0] = 1,空集只有一种构造方式
    *(numsTrees + 1) = 1; // 初始化numsTrees[1] = 1,一个数字只有一种构造方式

    // 获取使用nums可能异构的二叉查找树的数目
    int count = numTreesFromArray(numsTrees, nums, n);

    // 释放内存
    free(nums);
    free(numsTrees);

    return count;
}

/**
 * 返回由nums数组中的元素可以构造出的异构的二叉查找树的数目
 * Input numsTrees : 存储由i个元素可以构成的异构二叉查找树的数目
 * Input nums : 存储备选元素
 * Input size : 备选元素个数
 * Return : 异构的二叉查找树的数目
 */
int numTreesFromArray(int *numsTrees, int *nums, int size) {
    // 如果已经计算过,直接返回
    if (*(numsTrees + size))
        return *(numsTrees + size);

    int *littleNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点小的数
    int *bigNums = (int *)malloc(sizeof(int) * size); // 用于存储比根节点大的数
    int littleNumsCount, bigNumsCount, i, j, k, count = 0;
    for (i = 0; i < size; ++i) { // 遍历nums中的元素,将其作为根节点的值 
        littleNumsCount = 0; // 初始化littleNums的数量
        bigNumsCount = 0; // 初始化bigNums的数量

        // 将nums分成比根节点小的和比根节点大的两个数组
        for (j = 0; j < size; ++j) {
            if (*(nums + j) >= *(nums + i))
                break;
            *(littleNums + littleNumsCount++) = *(nums + j);
        }
        if (*(nums + j) == *(nums + i))
            ++j;
        for (; j < size; ++j)
            *(bigNums + bigNumsCount++) = *(nums + j);

        // 返回由littleNums可以构造出的异构的二叉查找树的数目
        j = numTreesFromArray(numsTrees, littleNums, littleNumsCount);
        // 返回由bigNums可以构造出的异构的二叉查找树的数目
        k = numTreesFromArray(numsTrees, bigNums, bigNumsCount);

        count += j * k; // 增加异构的二叉查找树的数目
    }

    // 释放内存
    free(littleNums);
    free(bigNums);

    // 存储结果
    *(numsTrees + size) = count;

    return count;
}

运行情况

Status:Accept
Time:0ms

测试数据

0   :   1
1   :   1
2   :   2
3   :   5
4   :   14
5   :   42
6   :   132
7   :   429
8   :   1430
9   :   4862
10  :   16796
11  :   58786
12  :   208012
13  :   742900
14  :   2674440
15  :   9694845
16  :   35357670
17  :   129644790
18  :   477638700
19  :   1767263190

总结

运行时间竟然是0ms,之前的时候可是超时的啊,可见有些时候用空间来换时间还是非常值得的。代码优化的重要性也不言而喻。


// 个人学习记录,若有错误请指正,大神勿喷
// sfg1991@163.com
// 2015-05-26

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值