import heapq

class Node:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None
        self.height = 1

class BST:
    """Standard Binary Search Tree Operations"""
    
    def new_node(self, data):
        return Node(data)

    def insert(self, root, data):
        if root is None: return Node(data)
        if data < root.data: root.left = self.insert(root.left, data)
        elif data > root.data: root.right = self.insert(root.right, data)
        return root

    def delete(self, root, data):
        if root is None: return root
        if data < root.data: root.left = self.delete(root.left, data)
        elif data > root.data: root.right = self.delete(root.right, data)
        else:
            if root.left is None: return root.right
            if root.right is None: return root.left
            temp = self.min_value(root.right)
            root.data = temp.data
            root.right = self.delete(root.right, temp.data)
        return root

    def search(self, root, data):
        if root is None or root.data == data: return root
        if data < root.data: return self.search(root.left, data)
        return self.search(root.right, data)

    def min_value(self, node):
        current = node
        while current.left: current = current.left
        return current

    # --- One-Liner Traversals (Return Lists) ---
    def inorder(self, root):
        return self.inorder(root.left) + [root.data] + self.inorder(root.right) if root else []

    def preorder(self, root):
        return [root.data] + self.preorder(root.left) + self.preorder(root.right) if root else []

    def postorder(self, root):
        return self.postorder(root.left) + self.postorder(root.right) + [root.data] if root else []

class AVL(BST):
    """AVL Specific Logic with auto-balancing"""
    
    def get_height(self, node): return node.height if node else 0
    
    def get_balance(self, node): 
        return (self.get_height(node.left) - self.get_height(node.right)) if node else 0

    def _rotate_right(self, y):
        x = y.left
        T2 = x.right
        x.right = y
        y.left = T2
        y.height = 1 + max(self.get_height(y.left), self.get_height(y.right))
        x.height = 1 + max(self.get_height(x.left), self.get_height(x.right))
        return x

    def _rotate_left(self, x):
        y = x.right
        T2 = y.left
        y.left = x
        x.right = T2
        x.height = 1 + max(self.get_height(x.left), self.get_height(x.right))
        y.height = 1 + max(self.get_height(y.left), self.get_height(y.right))
        return y

    def insert(self, root, data):
        # 1. Normal BST Insert
        if not root: return Node(data)
        if data < root.data: root.left = self.insert(root.left, data)
        elif data > root.data: root.right = self.insert(root.right, data)
        else: return root
        
        # 2. Update Height
        root.height = 1 + max(self.get_height(root.left), self.get_height(root.right))
        
        # 3. Balance
        b = self.get_balance(root)
        if b > 1 and data < root.left.data: return self._rotate_right(root) # LL
        if b < -1 and data > root.right.data: return self._rotate_left(root) # RR
        if b > 1 and data > root.left.data: # LR
            root.left = self._rotate_left(root.left)
            return self._rotate_right(root)
        if b < -1 and data < root.right.data: # RL
            root.right = self._rotate_right(root.right)
            return self._rotate_left(root)
        return root

    def delete(self, root, data):
        # 1. Normal BST Delete
        root = super().delete(root, data)
        if not root: return None
        
        # 2. Update Height & Balance
        root.height = 1 + max(self.get_height(root.left), self.get_height(root.right))
        b = self.get_balance(root)
        
        if b > 1 and self.get_balance(root.left) >= 0: return self._rotate_right(root)
        if b > 1 and self.get_balance(root.left) < 0:
            root.left = self._rotate_left(root.left)
            return self._rotate_right(root)
        if b < -1 and self.get_balance(root.right) <= 0: return self._rotate_left(root)
        if b < -1 and self.get_balance(root.right) > 0:
            root.right = self._rotate_right(root.right)
            return self._rotate_left(root)
        return root

    # Special Traversal for AVL problems asking for "Data(Balance)"
    def inorder_details(self, root):
        res = []
        if root:
            res += self.inorder_details(root.left)
            res.append(f"{root.data}({self.get_balance(root)})")
            res += self.inorder_details(root.right)
        return res
        
    def preorder_details(self, root):
        res = []
        if root:
            res.append(f"{root.data}({self.get_balance(root)})")
            res += self.preorder_details(root.left)
            res += self.preorder_details(root.right)
        return res

    def postorder_details(self, root):
        res = []
        if root:
            res += self.postorder_details(root.left)
            res += self.postorder_details(root.right)
            res.append(f"{root.data}({self.get_balance(root)})")
        return res

class Algo:
    """Static algorithms for Diameter, Huffman, LCA, etc."""
    
    @staticmethod
    def huffman(chars, freqs):
        # Returns codes in PRE-ORDER of the Huffman Tree
        class HNode:
            def __init__(self, c, f, i): self.c, self.f, self.i, self.left, self.right = c, f, i, None, None
            def __lt__(self, o): return self.f < o.f if self.f != o.f else self.i < o.i
            
        pq = [HNode(c, f, i) for i, (c, f) in enumerate(zip(chars, freqs))]
        heapq.heapify(pq)
        
        while len(pq) > 1:
            l, r = heapq.heappop(pq), heapq.heappop(pq)
            m = HNode(None, l.f + r.f, min(l.i, r.i))
            m.left, m.right = l, r
            heapq.heappush(pq, m)
            
        codes = {}
        def dfs(n, s):
            if not n: return
            if n.c: codes[n.c] = s
            dfs(n.left, s+'0'); dfs(n.right, s+'1')
        
        dfs(pq[0], "")
        # Collect codes in PreOrder logic
        res = []
        def pre(n):
            if not n: return
            if n.c: res.append(codes[n.c])
            pre(n.left); pre(n.right)
        pre(pq[0])
        return res

    @staticmethod
    def build_tree(pre_arr, in_arr):
        if not pre_arr: return None
        root = Node(pre_arr[0])
        mid = in_arr.index(pre_arr[0])
        root.left = Algo.build_tree(pre_arr[1:mid+1], in_arr[:mid])
        root.right = Algo.build_tree(pre_arr[mid+1:], in_arr[mid+1:])
        return root

    @staticmethod
    def height(root):
        return 1 + max(Algo.height(root.left), Algo.height(root.right)) if root else 0

    @staticmethod
    def diameter(root):
        ans = 0
        def d(n):
            nonlocal ans
            if not n: return 0
            l, r = d(n.left), d(n.right)
            ans = max(ans, l+r)
            return 1 + max(l, r)
        d(root)
        return ans
    
    @staticmethod
    def lca(root, n1, n2):
        while root:
            if root.data > n1 and root.data > n2: root = root.left
            elif root.data < n1 and root.data < n2: root = root.right
            else: break
        return root.data if root else None

