|
| 1 | +# https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ |
| 2 | + |
| 3 | +from typing import List, Optional |
| 4 | + |
| 5 | +# Definition for a binary tree node. |
| 6 | +class TreeNode: |
| 7 | + def __init__(self, val=0, left=None, right=None): |
| 8 | + self.val = val |
| 9 | + self.left = left |
| 10 | + self.right = right |
| 11 | + |
| 12 | +""" |
| 13 | +트리를 유일하게 복원하기 위해서는 inorder가 필요하다. inorder는 left/right child를 구분할 수 있기 때문이다. |
| 14 | + - preorder + inorder: root를 먼저 알고, inorder로 구조 구분 |
| 15 | + - postorder + postorder: root를 나중에 알고, inorder로 구조 구분 |
| 16 | +preorder + postorder라면, 구조가 다른 트리를 구분할 수 없는 경우가 있다. |
| 17 | +""" |
| 18 | + |
| 19 | +class Solution: |
| 20 | + def buildTree1(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
| 21 | + """ |
| 22 | + [Complexity] |
| 23 | + - TC: O(n^2) |
| 24 | + - SC: O(n^2) |
| 25 | + (* pop(0), index(), slicing이 모두 O(n)) |
| 26 | +
|
| 27 | + [Approach] |
| 28 | + preorder의 경우, root - left - right 순서로 방문하므로, root를 제일 먼저 찾을 수 있다. |
| 29 | + inorder의 경우, left - root - right 순서로 방문하므로, |
| 30 | + preorder의 매 단계에서 찾은 root에 대해 left와 right subtree를 다음과 같이 정의할 수 있다. (root 제외) |
| 31 | + - left subtree = inorder[:root_idx] |
| 32 | + - right subtree = inorder[root_idx + 1:] |
| 33 | + """ |
| 34 | + root = None |
| 35 | + |
| 36 | + if inorder: |
| 37 | + # preorder root |
| 38 | + root_idx = inorder.index(preorder.pop(0)) |
| 39 | + root = TreeNode(val=inorder[root_idx]) |
| 40 | + # preorder left |
| 41 | + root.left = self.buildTree(preorder, inorder[:root_idx]) |
| 42 | + # preorder right |
| 43 | + root.right = self.buildTree(preorder, inorder[root_idx + 1:]) |
| 44 | + |
| 45 | + return root |
| 46 | + |
| 47 | + def buildTree2(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
| 48 | + """ |
| 49 | + [Complexity] |
| 50 | + - TC: O(n^2) |
| 51 | + - SC: O(n^2) |
| 52 | + (* index(), slicing이 모두 O(n)) |
| 53 | +
|
| 54 | + [Approach] |
| 55 | + 이전 코드에서 pop(0)에 드는 O(n)을 줄이기 위해, |
| 56 | + preorder.reverse() 후 pop()으로 변경할 수 있다. |
| 57 | + """ |
| 58 | + preorder.reverse() |
| 59 | + |
| 60 | + def solve(preorder, inorder): |
| 61 | + if inorder: |
| 62 | + # preorder root |
| 63 | + root_idx = inorder.index(preorder.pop()) |
| 64 | + root = TreeNode(inorder[root_idx]) |
| 65 | + # preorder left |
| 66 | + root.left = solve(preorder, inorder[:root_idx]) |
| 67 | + # preorder right |
| 68 | + root.right = solve(preorder, inorder[root_idx + 1:]) |
| 69 | + |
| 70 | + return root |
| 71 | + |
| 72 | + return solve(preorder, inorder) |
| 73 | + |
| 74 | + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: |
| 75 | + """ |
| 76 | + [Complexity] |
| 77 | + - TC: O(n) |
| 78 | + - SC: O(n) (call stack) |
| 79 | +
|
| 80 | + [Approach] |
| 81 | + 병목이었던 index()와 slicing을 O(1)로 최적화 할 수 있다. |
| 82 | + - index(): inorder의 index를 dict로 캐싱 |
| 83 | + - slicing: start/end index로 추적 |
| 84 | + 이와 더불어 preorder의 root를 가리키는 index인 pre_idx를 사용하면 pop(0) 또는 pop()을 대체할 수도 있다. |
| 85 | + """ |
| 86 | + |
| 87 | + # instead of index() |
| 88 | + inorder_idx = {num: i for i, num in enumerate(inorder)} |
| 89 | + |
| 90 | + # preorder root (instead of pop()) |
| 91 | + pre_idx = 0 |
| 92 | + |
| 93 | + # instead of inorder[in_left:in_right] slicing |
| 94 | + def solve(in_left, in_right): |
| 95 | + nonlocal pre_idx |
| 96 | + |
| 97 | + # base condition |
| 98 | + if in_left >= in_right: |
| 99 | + return |
| 100 | + |
| 101 | + # preorder root |
| 102 | + root_val = preorder[pre_idx] |
| 103 | + root = TreeNode(root_val) |
| 104 | + |
| 105 | + # update indices |
| 106 | + root_idx = inorder_idx[root_val] |
| 107 | + pre_idx += 1 |
| 108 | + |
| 109 | + # recur |
| 110 | + root.left = solve(in_left, root_idx) |
| 111 | + root.right = solve(root_idx + 1, in_right) |
| 112 | + |
| 113 | + return root |
| 114 | + |
| 115 | + return solve(0, len(inorder)) |
0 commit comments