---
# System prepended metadata

title: Trees/ Tree Problems

---

# Binary - Trees

## Inorder traversal 
Left -> Node -> Right 
```python=
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = []
        def inorder(root): 
            if not root: 
                return 
            inorder(root.left) 
            res.append(root.val)
            inorder(root.right) 
        inorder(root) 
        return res
```
## Preorder Traversal
Node -> Left -> Right 
```python=
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = [] 
        def dfs(root): 
            if not root: 
                return  
            res.append(root.val) 
            dfs(root.left) 
            dfs(root.right) 
        dfs(root) 
        return res 
```
## Postorder traversal 
Left -> Right -> Node 
```python=
# 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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        res = [] 
        def dfs(root): 
            if not root: 
                return 
            dfs(root.left) 
            dfs(root.right) 
            res.append(root.val) 
        dfs(root) 
        return res
``` 

# Binary Search Tree (BST) 

## Binary Search Tree? 

Hiểu đơn giản thì đây là Binary Tree nhưng có quy tắc đối với mỗi Node, các Node bên phải đều lớn hơn nó và các Node bên trái phải bé hơn nó. 

## Problems 

### Convert Sorted Array -> BST: 
Sử dụng kĩ thuật `chia để trị - Divine and Conquer`: Node gốc là node giữa Array, đối với mỗi Node thì Node bên trái là  Node ở Giữa - `mid` của Array con bên trái, tương tự với Node phải chỉ ngược chiều. Ta làm tuần tự theo công thức đệ quy cho đến khi không thể chia đôi mảng thành trái-phải được nữa: 
```python
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def sortedArrayToBST(self, nums):
        """
        :type nums: List[int]
        :rtype: Optional[TreeNode]
        """
        def dnq(nums, l, r): 
            if l <= r: 
                mid = (l + r) // 2 
                node = TreeNode(nums[mid]) 
                node.left = dnq(nums, l, mid - 1) 
                node.right = dnq(nums, mid + 1, r)
            return node 
        return dnq(nums, 0, len(nums) - 1)
```
### Search in a BST 
Để tìm 1 giá trị trong BST nhìn trung đơn giản hơn do ta không cần dùng đến đệ quy mà chỉ cần vòng lặp là đủ: 

Đặc biệt với cấu trúc của BST, thay vì duyệt tất cả các Node ta có thể rút ngắn thời gian tìm kiếm còn $O(logn)$ bằng phương pháp tương tự như `binary search`: 
```python
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def searchBST(self, root, val):
        """
        :type root: Optional[TreeNode]
        :type val: int
        :rtype: Optional[TreeNode]
        """
        while root: 
            if root.val == val: 
                return root 
            elif root.val > val: 
                root = root.left
            else: 
                root = root.right 
        return None
```

### Validate BST 

Để kiểm tra 1 Binary Tree có phải là BST hay không, ta cần đảm bảo các điều kiện BST bằng cách: 
- Với Mỗi Node: nhánh bên phải chỉ được chứa các Node có giá trị cao hơn nó
- Với Mỗi Node: nhánh bên trái chỉ được chứa các Node có giá trị nhỏ hơn nó. 

=> Để làm được điều này, ta cần setup các giá trị `big` và `small`, dùng `dfs` duyệt tứng Node, và với mỗi Node: 
- Nó phải đủ điều kiện là nằm trong khoảng `(small, big)`, nếu lọt thì đây không phải BST. 
- Tiếp theo, với từng Node: 
    - Mỗi khi duyệt xuống nhánh trái: ta phải update `big` là giá trị của nó. 
    - Mỗi khi duyệt xuống nhánh phải: ta phải update `small` là giá trị của nó. 

```python
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isValidBST(self, root):
        """
        :type root: Optional[TreeNode]
        :rtype: bool
        """
        check = [0] 
        def dfs(root, big, small): 
            if not root: 
                return 
            elif root.val >= big or root.val <= small: 
                check[0] += 1 
                return 
            
            dfs(root.left, root.val, small) #update big if go left
            dfs(root.right, big, root.val) # update small if go right
        dfs(root, float('inf'), float('-inf')) 
        return check[0] == 0
```

### Delete a Node in BST 
Việc xóa Node mang giá trị `Key` trong BST khá tricky khi mà nó sẽ chia thành 2 trường hợp đối với cách giải đệ quy và 3 trường hợp khi dùng vòng lặp để xóa. 

Để xóa ta tiến hành 2 bước chính: 
- Tìm kiếm Node có giá trị `Key` để xóa. 
- Xóa Node đó. 

Để tìm 1 Node mang giá trị `Key` trong BST khá đơn giản và đã được trình bày từ trước. 

Cái khó ở đây là khi đã tìm được Node mang `Key` rồi thì ta cần xử lý trường hợp:
1. Node là Node lá hoặc Node chỉ có 1 con (trường hợp này được tách ra trong cách giải vòng lặp). 
    - Ta sẽ tiến hành nối Node con của nó với cha. Hoặc nối Node cha với Null khi Node là Node lá.
2. Node là Node trong(tức là có 2 con). 
    - Khi này ta cần tìm `inorder successor - Node nhánh phải bé nhất` của Node hiện tại. Sau đó tiến hành thay giá trị của Node này bằng giá trị của `inorder successor`. Sau đó, xóa node `inorder successor`. 

#### Sử dụng vòng lặp: 
```python
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def deleteNode(self, root, key):
        """
        :type root: Optional[TreeNode]
        :type key: int
        :rtype: Optional[TreeNode]
        """
        def find_successor(root): #Hàm tìm node nhỏ nhất bên trái của node hiện tại, đồng thời node cha của nó
            prev = None  
            while root: 
                if not root.left:  
                    break 
                prev = root 
                root = root.left 
            return prev, root 
        pre = None #Thiết lập con trỏ trước con trỏ sau
        cur = root 
        while cur: 
            if key > cur.val: #Tiến hành duyệt cây theo logic BST.
                pre = cur
                cur = cur.right 
            elif key < cur.val: 
                pre = cur 
                cur = cur.left 
            elif key == cur.val: #Nếu ta gặp node có chứa giá trị key, ta tiến hành xóa node đó theo 3 trường hợp đã được định nghĩa trên.
                if not cur.left and not cur.right: #Nếu là node lá
                    if pre == None: #Trường hợp chỉ có 1 node
                        root = None 
                        break 
                    elif pre.left == cur: #nếu đây là node lá trái của node cha
                        pre.left = None 
                        break
                    else: #nếu là node phải
                        pre.right = None
                        break 
                elif not cur.left or not cur.right: #Nếu node chỉ có 1 con 
                    if pre == None: #Trường hợp cần xóa là root. Ta xóa root và gán node mới là left hoặc right của nó tùy thuộc theo việc bên nào có tồn tại. 
                        if root.left: 
                            root = root.left
                            break
                        else: 
                            root = root.right 
                            break 
                    elif not cur.left: #Nếu chỉ có node con phải, ta sẽ tiến hành nối node cha của nó vào node phải của nó, tương tự với trường hợp chỉ có node trái ở dưới 
                        if pre.left == cur: 
                            pre.left = cur.right 
                            break
                        else: 
                            pre.right = cur.right 
                            break 
                    else: 
                        if pre.left == cur: 
                            pre.left = cur.left 
                            break
                        else: 
                            pre.right = cur.left
                            break 
                else: # Nếu gặp trường hợp cần xóa 1 node trong, ta sẽ cần phải thay đổi node đó thành node lớn nhất bên phải (inorder successor), đặc biệt nếu nó có node con ta cũng cần phải nối cả chúng vào nữa
                    prev, successor = find_successor(cur.right) 
                    cur.val = successor.val 
                    if prev != None:
                        prev.left = successor.right
                    else: 
                        cur.right = successor.right
                    break
        return root
```
#### Giải theo đệ quy: 
```python
# 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 deleteNode(self, root: Optional[TreeNode], key: int) -> Optional[TreeNode]:
        if not root: #Basecase 1: Nếu node là Null, return 
            return root 
        # Tiến hành tìm kiếm Node có giá trị Key trong BST 
        # Đặc biệt, nếu ta có thể tìm được node chứa Key trong 1 trong các nhánh
        # trái hoặc phải của node hiện tại, ta sẽ tiến hành nối node hiện 
        # tại với nhánh đã bị biến đổi (cắt đi 1 node) 

        elif root.val < key: 
            root.right = self.deleteNode(root.right, key)
        elif root.val > key: 
            root.left = self.deleteNode(root.left, key)

        # Nếu tìm được node chứa Key: 
        # TH1: Node chỉ có 1 con hoặc là node lá, ta nối node cha của nó
        # vào node con của nó là xong
        elif root.val == key: 
            if not root.left: 
                return root.right 
            elif not root.right: 
                return root.left
            
            # TH2: Node là node trong (2 node): Ta cần thay giá trị của node 
            # bằng giá trị của node nhỏ nhất bên phải (inorder successor), 
            # sau đó, xóa node nhỏ nhất đó (điều này đơn giản vì nó sẽ là node rơi vào TH1)
            else:
                cur = root.right 
                while cur.left: 
                    cur = cur.left
                root.val = cur.val 
                root.right = self.deleteNode(root.right, cur.val) 
        return root
```