link

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        """tc O(N) sc O(1)
        main idea: (recursion) for each subtree there is such structure
                    root
                    / \
                left   right 
        we can always get root node from first position of preorder, but the 
        diffculty part for root node is we don't know where the left subtree node start, same as left subtree
        we can base on root node to refer inorder to get left child's position  
        Note,  the next element in preorder is root node of its left subtree at the same time when it is left node of current node 
        """
        # base case there is no parent/ children node => return None 
        if not preorder or not inorder:
            return None
        root_v = P.pop(0)
        root = TreeNode(root_v)
        idx = inorder.index(root_v)
        root.left = self.buildTree(preorder,inorder[:idx])# sperate left subtree 
        root.right = self.buildTree(preorder,inorder[idx+1:])  # seperate right subtree 
        return root
    

optimal

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    # time O(N) space(N)
    # main idea: to reduce time cost of list.pop(0) O(N) at each recursion, use iter to get the first number,  
    # to reduce the O(N) time cost of list.index(val), use dictionary to save index of inorder according to value  
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        inorder_d = {}
        for idx, num in enumerate(inorder):
            inorder_d[num] = idx
        pre_iter = iter(preorder)
        
        def helper(start,end):
            if start > end: return None
            root_val = next(pre_iter)
            root = TreeNode(root_val)
            idx = inorder_d[root.val]
            root.left = helper(start,idx-1)
            root.right = helper(idx+1,end)
            return root
        
        return helper(0,len(inorder)-1)

note: time complexity of list.index(val) is O(N)

straightforward solution

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        ni = len(inorder)
        np = len(preorder)
        if ni != np:
            return None 
        def helper(preorder,inorder,preL,preH,inL,inH):
            if preL > preH or inL > inH: return None 
            root = TreeNode(preorder[preL])
            # find inorder root idx 
            inorder_root = inL
            for i in range(inL,inH+1):
                if inorder[i] == root.val:
                    inorder_root = i 
                    break
            # caculate left tree length
            leftTreeLen = inorder_root - inL 
            root.left = helper(preorder,inorder,preL+1,preL+leftTreeLen,inL,inorder_root-1)
            root.right = helper(preorder,inorder,preL+leftTreeLen+1,preH,inorder_root+1,inH)
            return root 
        return helper(preorder,inorder,0,np-1,0,np-1)