LC 0096 [M] Unique Binary Search Trees - ALawliet/algorithms GitHub Wiki

O(n^2) because if we had the numbers 1 2 3 4 5
we much consider each as the root node
class Solution:
    def numTrees(self, n: int) -> int:
        # numTree[4] = numTree[0] * numTree[3] +
        #      numTree[1] * numTree[2] +
        #      numTree[2] * numTree[1] +
        #      numTree[3] * numTree[1]
        numTree = [1] * (n + 1)
        
        # 0 nodes = 1 tree (empty tree)
        # 1 nodes = 1 tree (root node)
        for nodes in range(2, n + 1):
            total = 0
            for root in range(1, nodes + 1):
                left = root - 1
                right = nodes - root
                total += numTree[left] * numTree[right]
            numTree[nodes] = total
        return numTree[n]

idea is to count at the root combinations = numTrees(number in left) * numTrees(number in right)

so it's a bottom-up solution so post-order/DP

class Solution:
    def numTrees(self, n: int) -> int:
        memo = {}
        
        def postorder(n):
          if n in memo:
            return memo[n]
          if n <= 1:
             return 1

          count = 0
          for i in range(1, n+1):
            countLeftSubtrees = postorder(i - 1)
            countRightSubtrees = postorder(n - i)
            count += (countLeftSubtrees * countRightSubtrees)

          memo[n] = count
          return count
        
        return postorder(n)