101 Logo
onenoughtone

Code Implementation

Tree Structure Counter Implementation

Below is the implementation of the tree structure counter:

solution.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* Recursive approach (brute force)
* Time Complexity: O(4^n / √n) - Exponential due to repeated calculations
* Space Complexity: O(n) - Recursion stack
*
* @param {number} n - Number of nodes
* @return {number} - Number of unique BSTs
*/
function numTreesRecursive(n) {
// Base case: empty tree or single node tree
if (n === 0 || n === 1) {
return 1;
}
let total = 0;
// Try each value as the root
for (let i = 1; i <= n; i++) {
// Number of unique left subtrees * Number of unique right subtrees
const leftTrees = numTreesRecursive(i - 1);
const rightTrees = numTreesRecursive(n - i);
total += leftTrees * rightTrees;
}
return total;
}
/**
* Memoization approach (top-down dynamic programming)
* Time Complexity: O(n^2) - Each value of n is computed once, with n iterations for each
* Space Complexity: O(n) - For memoization and recursion stack
*
* @param {number} n - Number of nodes
* @return {number} - Number of unique BSTs
*/
function numTreesMemo(n) {
// Create memo array to store results
const memo = new Array(n + 1).fill(-1);
function calculate(n) {
// If already computed, return from memo
if (memo[n] !== -1) {
return memo[n];
}
// Base case: empty tree or single node tree
if (n === 0 || n === 1) {
return 1;
}
let total = 0;
// Try each value as the root
for (let i = 1; i <= n; i++) {
// Number of unique left subtrees * Number of unique right subtrees
const leftTrees = calculate(i - 1);
const rightTrees = calculate(n - i);
total += leftTrees * rightTrees;
}
// Store result in memo
memo[n] = total;
return total;
}
return calculate(n);
}
/**
* Tabulation approach (bottom-up dynamic programming)
* Time Complexity: O(n^2) - Two nested loops
* Space Complexity: O(n) - For the DP array
*
* @param {number} n - Number of nodes
* @return {number} - Number of unique BSTs
*/
function numTreesDP(n) {
// Create DP array
const dp = new Array(n + 1).fill(0);
// Base cases
dp[0] = 1;
dp[1] = 1;
// Fill DP array bottom-up
for (let i = 2; i <= n; i++) {
for (let j = 1; j <= i; j++) {
// Number of unique BSTs with root j
// Left subtree: j-1 nodes, Right subtree: i-j nodes
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
/**
* Mathematical approach (Catalan Numbers)
* Time Complexity: O(n) - Single loop
* Space Complexity: O(1) - Constant extra space
*
* @param {number} n - Number of nodes
* @return {number} - Number of unique BSTs
*/
function numTrees(n) {
// C(n) = C(n-1) * (4n - 2) / (n + 1)
let result = 1;
for (let i = 1; i <= n; i++) {
result = result * (4 * i - 2) / (i + 1);
}
return Math.round(result); // Round to handle potential floating-point errors
}
// Test cases
console.log(numTrees(1)); // 1
console.log(numTrees(2)); // 2
console.log(numTrees(3)); // 5
console.log(numTrees(4)); // 14
console.log(numTrees(5)); // 42

Step-by-Step Explanation

Let's break down the implementation:

  1. 1. Understand the Problem: First, understand that we need to count the number of structurally unique BSTs that can be formed with n nodes labeled from 1 to n.
  2. 2. Identify the Recursive Structure: Recognize that for each root value i, the left subtree contains nodes 1 to i-1, and the right subtree contains nodes i+1 to n.
  3. 3. Implement Recursive Solution: Start with a recursive solution to understand the problem better, calculating the number of unique BSTs for each possible root value.
  4. 4. Optimize with Memoization: Notice that the recursive solution has overlapping subproblems. Use memoization to avoid redundant calculations.
  5. 5. Convert to Bottom-Up DP: Transform the recursive solution into an iterative one using a bottom-up dynamic programming approach.
  6. 6. Recognize the Mathematical Pattern: Observe that the solution follows the pattern of Catalan numbers, which can be calculated using a direct formula.
  7. 7. Implement the Mathematical Solution: Use the Catalan number formula to calculate the answer directly, without using dynamic programming.
  8. 8. Test with Various Inputs: Verify the solution with different test cases, including edge cases like n = 0 and n = 1.
ProblemSolutionCode
101 Logo
onenoughtone