
'''
# Leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal

use **recursive** to solve this problem.

## Time and Space Complexity

### A. recursive & change range of preorder and inorder

```
TC: O(n)
SC: O(n)
```

### B. recursive & search index (of inorder)

```
TC: O(n^2)
SC: O(n)
```

### C. recursive & hash table

```
TC: O(n)
SC: O(n)
```

'''
class Solution:
    '''
    A. 재귀 풀이
    preorder와 inorder의 각각의 범위를 조정하여 트리를 생성
    '''
    def buildTreeA(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        def setTree(pre_left, pre_right, in_left, in_right):
            # 재귀 종료 조건: preorder 범위가 유효하지 않은 경우
            if pre_left > pre_right:
                return None

            val = preorder[pre_left]  # preorder의 현재 루트 노드 값 가져오기
            mid = TreeNode(val)  # 루트 노드를 먼저 생성

            mid_inorder = inorder_idx_map[val]  # 루트 노드의 inorder 인덱스 가져오기
            left_size = mid_inorder - in_left  # 왼쪽 서브트리의 크기 계산

            # 왼쪽 서브트리 생성: preorder와 inorder의 범위를 왼쪽 서브트리로 조정
            mid.left = setTree(
                pre_left + 1, pre_left + left_size, in_left, mid_inorder - 1
            )

            # 오른쪽 서브트리 생성: preorder와 inorder의 범위를 오른쪽 서브트리로 조정
            mid.right = setTree(
                pre_left + left_size + 1, pre_right, mid_inorder + 1, in_right
            )

            return mid # 현재 노드 반환

        # inorder를 값 -> 인덱스 맵핑한 딕셔너리 생성 - TC: O(n), SC: O(n)
        inorder_idx_map = {value: idx for idx, value in enumerate(inorder)}

        # 트리 생성 시작 (preorder와 inorder 전체 범위 사용) - TC: O(n), SC: O(n)
        return setTree(0, len(preorder) - 1, 0, len(inorder) - 1)
        

    '''
    # B. 재귀 풀이 + 공간 최적화
    # 레퍼런스 링크의 풀이 2: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
    # 특징: 순회 시마다 인덱스를 찾는 과정이 있음
    '''
    def buildTreeB(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        # pre: 현재 preorder에서 확인할 인덱스
        # start, end: inorder에서 사용할 시작/종료 범위
        def setTree(pre, start, end):
            # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
            if not (pre < len(preorder) and start <= end): # preorder에서 확인할 인덱스가 범위에서 나감, 투 포인터가 만남
                return None
            
            val = preorder[pre] # 현재 노드의 값
            root = inorder.index(val) # 트리/서브트리의 루트 노드 인덱스 찾기 - TC: O(n)
            
            left = setTree(pre + 1, start, root - 1) 
            # inorder에서 root노드의 왼쪽은 왼쪽 서브트리
            # pre의 변화: 왼쪽 서브트리의 루트 노드를 찾기 위해 +1 이동

            right = setTree(pre + 1 + root - start, root + 1, end)
            # inorder에서 root노드의 오른쪽은 오른쪽 서브트리
            # pre의 변화: 오른쪽 서브트리의 루트 노드를 찾기 위해 +1 이동 + (root - start) 👈 왼쪽 서브트리의 크기 만큼 더 이동

            return TreeNode(preorder[pre], left, right) # 트리 노드 생성
        
        # preorder 최초 인덱스 = 루트 노드(0), inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스
        return setTree(0, 0, len(inorder) - 1) # TC: O(n^2), SC: O(n)

    '''
    C. 재귀 풀이 + 시간 최적화
    레퍼런스 링크의 풀이 3: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
    특징: A에서 preorder를 찾는 O(n) 과정을 해시 테이블을 사용하여 O(1)로 최적화
    '''
    def buildTreeC(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        # enumerate: 인덱스와 값을 동시에 반환
        # inorder를 val -> idx로 매핑한 딕셔너리 생성
        inorder_index_map = {val: idx for idx, val in enumerate(inorder)} 
        # preorder를 순회하기 위한 iterator 객체 생성
        pre_iter = iter(preorder) 

        def setTree(start, end):
            if start > end: # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
                return None

            root_val = next(pre_iter) # 현재 노드의 값, 매 순회마다 다음 preorder 노드(root)의 값을 가져옴
            root = inorder_index_map[root_val] # 트리/서브트리의 루트 노드 인덱스를 O(1) 시간으로 찾기

            left = setTree(start, root - 1) # 왼쪽 서브트리
            right = setTree(root + 1, end) # 오른쪽 서브트리
            return TreeNode(root_val, left, right) # 트리 노드 생성
        
        return setTree(0, len(inorder) - 1) # inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스
