Oh yes. It took me a while to see what you are saying. Subtrees could be reused with the DP approach. Not only it saves time but also space is freed from creating extra nodes of the same value. Although having trees that share branches could be tricky to deal with in the real world, it is the best solution for an algorithm exercise indeed.
Small modification: Instead of reversed(range(1,i)), I did xrange(i-1, 0, -1) and all range -> xrange.... this went from 88% to 96%.
def generateTrees(self, n):
if n == 0:
tree_list = [[[None]] * (n + 2) for i in xrange( n+ 2)]
for i in xrange(1, n+1):
tree_list[i][i] = [TreeNode(i)]
for j in xrange(i-1, 0, -1):
tree_list[j][i] = 
for k in xrange(j, i+1):
for left in tree_list[j][k-1]:
for right in tree_list[k+1][i]:
root = TreeNode(k)
(root.left, root.right) = (left, right)