Skip to content

Split View: FAANG 코딩 인터뷰 완전 정복: 트리와 그래프 핵심 패턴

|

FAANG 코딩 인터뷰 완전 정복: 트리와 그래프 핵심 패턴

FAANG 코딩 인터뷰 완전 정복: 트리와 그래프 핵심 패턴

트리와 그래프 문제는 FAANG 인터뷰에서 배열/문자열 다음으로 출제 빈도가 높습니다. 특히 Google, Meta에서 그래프와 트리 탐색 문제를 매우 중요하게 봅니다. 이 글에서는 인터뷰 필수 패턴 5가지를 완전한 Python 코드와 함께 정복합니다.


1. Binary Tree 핵심 패턴

DFS는 깊이 우선으로 트리를 탐색합니다. 재귀와 반복(스택) 두 가지 방식으로 구현할 수 있습니다.

트리 노드 정의

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

DFS 순회 3가지 (재귀)

# Inorder: 왼쪽 → 현재 → 오른쪽 (BST에서 정렬된 순서)
def inorder(root):
    if not root:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

# Preorder: 현재 → 왼쪽 → 오른쪽 (트리 복사, 직렬화)
def preorder(root):
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

# Postorder: 왼쪽 → 오른쪽 → 현재 (디렉토리 크기 계산)
def postorder(root):
    if not root:
        return []
    return postorder(root.left) + postorder(root.right) + [root.val]

DFS 반복 구현 (스택 사용)

def inorderIterative(root):
    result = []
    stack = []
    current = root

    while current or stack:
        # 가장 왼쪽으로 이동
        while current:
            stack.append(current)
            current = current.left

        current = stack.pop()
        result.append(current.val)
        current = current.right

    return result

def preorderIterative(root):
    if not root:
        return []

    result = []
    stack = [root]

    while stack:
        node = stack.pop()
        result.append(node.val)

        # 오른쪽을 먼저 스택에 넣어야 왼쪽이 먼저 처리됨
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

    return result

BFS (Breadth-First Search) - Level Order Traversal

from collections import deque

def levelOrder(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        current_level = []

        for _ in range(level_size):
            node = queue.popleft()
            current_level.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(current_level)

    return result

# 예시
# 트리:     3
#          / \
#         9  20
#            / \
#           15   7
# Output: [[3], [9,20], [15,7]]

문제 1: Maximum Depth of Binary Tree (LeetCode 104)

def maxDepth(root) -> int:
    if not root:
        return 0
    return 1 + max(maxDepth(root.left), maxDepth(root.right))

# BFS 방식
def maxDepthBFS(root) -> int:
    if not root:
        return 0

    depth = 0
    queue = deque([root])

    while queue:
        depth += 1
        for _ in range(len(queue)):
            node = queue.popleft()
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

    return depth

시간 복잡도: O(n) - 모든 노드 방문 공간 복잡도: O(h) - h는 트리 높이 (최악 O(n) for skewed tree)


문제 2: Invert Binary Tree (LeetCode 226)

def invertTree(root):
    if not root:
        return None

    # 왼쪽과 오른쪽 서브트리를 교환
    root.left, root.right = invertTree(root.right), invertTree(root.left)
    return root

# 반복 방식 (BFS)
def invertTreeBFS(root):
    if not root:
        return None

    queue = deque([root])
    while queue:
        node = queue.popleft()
        node.left, node.right = node.right, node.left

        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

    return root

시간 복잡도: O(n) 공간 복잡도: O(n) for balanced tree, O(n) worst case


문제 3: Symmetric Tree (LeetCode 101)

트리가 대칭인지 확인하라.

def isSymmetric(root) -> bool:
    def isMirror(left, right):
        if not left and not right:
            return True
        if not left or not right:
            return False
        return (left.val == right.val and
                isMirror(left.left, right.right) and
                isMirror(left.right, right.left))

    return isMirror(root.left, root.right)

# 반복 방식
def isSymmetricIterative(root) -> bool:
    queue = deque([(root.left, root.right)])

    while queue:
        left, right = queue.popleft()

        if not left and not right:
            continue
        if not left or not right:
            return False
        if left.val != right.val:
            return False

        queue.append((left.left, right.right))
        queue.append((left.right, right.left))

    return True

문제 4: Path Sum II (LeetCode 113)

루트에서 리프까지 합이 targetSum이 되는 모든 경로를 찾아라.

def pathSum(root, targetSum: int):
    result = []

    def dfs(node, current_path, remaining):
        if not node:
            return

        current_path.append(node.val)
        remaining -= node.val

        # 리프 노드이고 합이 맞으면 경로 추가
        if not node.left and not node.right and remaining == 0:
            result.append(list(current_path))
        else:
            dfs(node.left, current_path, remaining)
            dfs(node.right, current_path, remaining)

        # 백트래킹: 현재 노드 제거
        current_path.pop()

    dfs(root, [], targetSum)
    return result

문제 5: Lowest Common Ancestor (LeetCode 236)

두 노드 p, q의 LCA(최소 공통 조상)를 찾아라.

def lowestCommonAncestor(root, p, q):
    if not root or root == p or root == q:
        return root

    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)

    # 양쪽에서 발견되면 현재 노드가 LCA
    if left and right:
        return root
    # 한쪽에서만 발견되면 그쪽이 LCA
    return left if left else right

# 예시
# 트리:     3
#          / \
#         5   1
#        / \ / \
#       6  2 0  8
#         / \
#        7   4
# p=5, q=1 → LCA = 3
# p=5, q=4 → LCA = 5

문제 6: Serialize and Deserialize Binary Tree (LeetCode 297) - 고난도

class Codec:
    def serialize(self, root) -> str:
        """BFS를 사용해 트리를 문자열로 직렬화"""
        if not root:
            return ""

        result = []
        queue = deque([root])

        while queue:
            node = queue.popleft()
            if node:
                result.append(str(node.val))
                queue.append(node.left)
                queue.append(node.right)
            else:
                result.append("null")

        return ",".join(result)

    def deserialize(self, data: str):
        """직렬화된 문자열에서 트리 복원"""
        if not data:
            return None

        values = data.split(",")
        root = TreeNode(int(values[0]))
        queue = deque([root])
        i = 1

        while queue:
            node = queue.popleft()

            if i < len(values) and values[i] != "null":
                node.left = TreeNode(int(values[i]))
                queue.append(node.left)
            i += 1

            if i < len(values) and values[i] != "null":
                node.right = TreeNode(int(values[i]))
                queue.append(node.right)
            i += 1

        return root

시간 복잡도: O(n) - 모든 노드 방문 공간 복잡도: O(n) - 직렬화 결과 저장


2. Binary Search Tree (BST) 패턴

BST 특성

  • 왼쪽 서브트리의 모든 값 < 현재 노드 값
  • 오른쪽 서브트리의 모든 값 > 현재 노드 값
  • Inorder 순회 시 정렬된 순서로 방문

문제 7: Validate Binary Search Tree (LeetCode 98)

def isValidBST(root) -> bool:
    def validate(node, min_val, max_val):
        if not node:
            return True

        if node.val <= min_val or node.val >= max_val:
            return False

        return (validate(node.left, min_val, node.val) and
                validate(node.right, node.val, max_val))

    return validate(root, float('-inf'), float('inf'))

# 함정: 단순히 left.val < root.val < right.val 만 체크하는 것은 틀림
# 예시:
#     5
#    / \
#   1   4
#      / \
#     3   6
# root.right.left = 3 (3 < 5 이므로 BST 위반)

문제 8: Kth Smallest Element in BST (LeetCode 230)

def kthSmallest(root, k: int) -> int:
    # Inorder 순회가 정렬된 순서이므로 k번째가 답
    count = [0]
    result = [0]

    def inorder(node):
        if not node or count[0] >= k:
            return

        inorder(node.left)
        count[0] += 1
        if count[0] == k:
            result[0] = node.val
            return
        inorder(node.right)

    inorder(root)
    return result[0]

# 반복 방식 (더 직관적)
def kthSmallestIterative(root, k: int) -> int:
    stack = []
    current = root

    while current or stack:
        while current:
            stack.append(current)
            current = current.left

        current = stack.pop()
        k -= 1
        if k == 0:
            return current.val
        current = current.right

    return -1

문제 9: Convert Sorted Array to BST (LeetCode 108)

정렬된 배열을 높이 균형 BST로 변환하라.

def sortedArrayToBST(nums: list[int]):
    def buildBST(left, right):
        if left > right:
            return None

        # 중간 값을 루트로 선택 (균형 보장)
        mid = left + (right - left) // 2
        root = TreeNode(nums[mid])
        root.left = buildBST(left, mid - 1)
        root.right = buildBST(mid + 1, right)
        return root

    return buildBST(0, len(nums) - 1)

3. Graph BFS/DFS 패턴

그래프 표현

# 인접 리스트 (권장)
graph = {
    0: [1, 2],
    1: [0, 3],
    2: [0, 4],
    3: [1],
    4: [2]
}

# 인접 행렬
matrix = [
    [0, 1, 1, 0, 0],
    [1, 0, 0, 1, 0],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 0, 0],
    [0, 0, 1, 0, 0]
]

방문 처리

# DFS (재귀)
def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()

    visited.add(start)
    print(start, end=' ')

    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

# BFS
def bfs(graph, start):
    visited = set([start])
    queue = deque([start])

    while queue:
        node = queue.popleft()
        print(node, end=' ')

        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

문제 10: Number of Islands (LeetCode 200)

def numIslands(grid: list[list[str]]) -> int:
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        # 범위 벗어나거나 물이면 종료
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
            return

        # 방문 표시 (물로 변경)
        grid[r][c] = '0'

        # 4방향 탐색
        dfs(r + 1, c)
        dfs(r - 1, c)
        dfs(r, c + 1)
        dfs(r, c - 1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                dfs(r, c)

    return count

# 예시
# Input: grid = [
#   ["1","1","0","0","0"],
#   ["1","1","0","0","0"],
#   ["0","0","1","0","0"],
#   ["0","0","0","1","1"]
# ]
# Output: 3

시간 복잡도: O(mn) 공간 복잡도: O(mn) - 재귀 스택


문제 11: Clone Graph (LeetCode 133)

class Node:
    def __init__(self, val=0, neighbors=None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []

def cloneGraph(node):
    if not node:
        return None

    cloned = {}  # 원본 노드 → 복제 노드 매핑

    def dfs(original):
        if original in cloned:
            return cloned[original]

        # 복제 노드 생성
        copy = Node(original.val)
        cloned[original] = copy

        # 이웃 재귀 복제
        for neighbor in original.neighbors:
            copy.neighbors.append(dfs(neighbor))

        return copy

    return dfs(node)

시간 복잡도: O(V + E) - 모든 정점과 간선 방문 공간 복잡도: O(V) - 복제 매핑 저장


문제 12: Course Schedule (LeetCode 207) - 위상 정렬

선수과목 관계가 주어질 때 모든 과목을 수강할 수 있는지 판별하라.

from collections import defaultdict, deque

def canFinish(numCourses: int, prerequisites: list[list[int]]) -> bool:
    # 그래프 구성
    graph = defaultdict(list)
    in_degree = [0] * numCourses

    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1

    # 진입 차수가 0인 노드에서 시작 (선수과목 없는 과목)
    queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
    completed = 0

    while queue:
        course = queue.popleft()
        completed += 1

        for next_course in graph[course]:
            in_degree[next_course] -= 1
            if in_degree[next_course] == 0:
                queue.append(next_course)

    # 모든 과목을 완료했으면 사이클 없음
    return completed == numCourses

# DFS로 사이클 감지
def canFinishDFS(numCourses: int, prerequisites: list[list[int]]) -> bool:
    graph = defaultdict(list)
    for course, prereq in prerequisites:
        graph[prereq].append(course)

    # 상태: 0=미방문, 1=방문중, 2=완료
    state = [0] * numCourses

    def hasCycle(node):
        if state[node] == 1:  # 사이클 발견
            return True
        if state[node] == 2:  # 이미 처리됨
            return False

        state[node] = 1  # 방문 시작
        for neighbor in graph[node]:
            if hasCycle(neighbor):
                return True
        state[node] = 2  # 방문 완료
        return False

    for i in range(numCourses):
        if hasCycle(i):
            return False

    return True

문제 13: Pacific Atlantic Water Flow (LeetCode 417)

def pacificAtlantic(heights: list[list[int]]) -> list[list[int]]:
    if not heights:
        return []

    rows, cols = len(heights), len(heights[0])
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

    def bfs(starts):
        visited = set(starts)
        queue = deque(starts)

        while queue:
            r, c = queue.popleft()
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if (0 <= nr < rows and 0 <= nc < cols and
                    (nr, nc) not in visited and
                    heights[nr][nc] >= heights[r][c]):  # 역방향 탐색
                    visited.add((nr, nc))
                    queue.append((nr, nc))
        return visited

    # 태평양 (상단 + 좌측 경계)
    pacific_starts = [(0, c) for c in range(cols)] + [(r, 0) for r in range(1, rows)]
    # 대서양 (하단 + 우측 경계)
    atlantic_starts = [(rows-1, c) for c in range(cols)] + [(r, cols-1) for r in range(rows-1)]

    pacific = bfs(pacific_starts)
    atlantic = bfs(atlantic_starts)

    return [[r, c] for r, c in pacific & atlantic]

문제 14: Word Ladder (LeetCode 127)

BFS로 단어 변환 최단 경로를 구하라.

def ladderLength(beginWord: str, endWord: str, wordList: list[str]) -> int:
    word_set = set(wordList)
    if endWord not in word_set:
        return 0

    queue = deque([(beginWord, 1)])
    visited = {beginWord}

    while queue:
        word, steps = queue.popleft()

        for i in range(len(word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                new_word = word[:i] + c + word[i+1:]

                if new_word == endWord:
                    return steps + 1

                if new_word in word_set and new_word not in visited:
                    visited.add(new_word)
                    queue.append((new_word, steps + 1))

    return 0

# 예시
# beginWord = "hit", endWord = "cog"
# wordList = ["hot","dot","dog","lot","log","cog"]
# Output: 5 (hit→hot→dot→dog→cog)

시간 복잡도: O(M^2 _ N) - M은 단어 길이, N은 단어 수 공간 복잡도: O(M^2 _ N)


4. Union Find (Disjoint Set) 패턴

구현

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))  # 각 노드의 부모
        self.rank = [0] * n           # 트리 높이

    def find(self, x):
        # Path Compression
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py:
            return False  # 이미 같은 집합

        # Union by Rank
        if self.rank[px] < self.rank[py]:
            px, py = py, px
        self.parent[py] = px
        if self.rank[px] == self.rank[py]:
            self.rank[px] += 1
        return True

    def connected(self, x, y):
        return self.find(x) == self.find(y)

문제 15: Number of Connected Components (LeetCode 323)

def countComponents(n: int, edges: list[list[int]]) -> int:
    uf = UnionFind(n)
    components = n

    for u, v in edges:
        if uf.union(u, v):
            components -= 1  # 두 컴포넌트가 합쳐지면 감소

    return components

문제 16: Redundant Connection (LeetCode 684)

트리에 하나의 중복 간선이 추가되었을 때, 그 간선을 찾아라.

def findRedundantConnection(edges: list[list[int]]) -> list[int]:
    n = len(edges)
    uf = UnionFind(n + 1)

    for u, v in edges:
        if not uf.union(u, v):
            return [u, v]  # 이미 연결된 경우 → 중복 간선

    return []

문제 17: Accounts Merge (LeetCode 721)

def accountsMerge(accounts: list[list[str]]) -> list[list[str]]:
    email_to_id = {}   # 이메일 → ID
    email_to_name = {} # 이메일 → 이름

    # 모든 이메일에 고유 ID 부여
    for i, account in enumerate(accounts):
        name = account[0]
        for email in account[1:]:
            if email not in email_to_id:
                email_to_id[email] = len(email_to_id)
            email_to_name[email] = name

    uf = UnionFind(len(email_to_id))

    # 같은 계정의 이메일들을 Union
    for account in accounts:
        first_email = account[1]
        first_id = email_to_id[first_email]
        for email in account[2:]:
            uf.union(first_id, email_to_id[email])

    # 같은 그룹의 이메일들을 모음
    from collections import defaultdict
    groups = defaultdict(list)
    for email, eid in email_to_id.items():
        root = uf.find(eid)
        groups[root].append(email)

    result = []
    for root, emails in groups.items():
        name = email_to_name[emails[0]]
        result.append([name] + sorted(emails))

    return result

5. Shortest Path 알고리즘

Dijkstra's Algorithm

음수 가중치가 없는 그래프에서 단일 출발점 최단 경로를 구합니다.

import heapq
from collections import defaultdict

def dijkstra(graph: dict, start: int) -> dict:
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    priority_queue = [(0, start)]  # (거리, 노드)

    while priority_queue:
        current_dist, current_node = heapq.heappop(priority_queue)

        # 더 짧은 경로가 이미 있으면 스킵
        if current_dist > distances[current_node]:
            continue

        for neighbor, weight in graph[current_node]:
            distance = current_dist + weight

            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(priority_queue, (distance, neighbor))

    return distances

# 예시
graph = {
    0: [(1, 4), (2, 1)],
    1: [(3, 1)],
    2: [(1, 2), (3, 5)],
    3: []
}
# distances from 0: {0: 0, 1: 3, 2: 1, 3: 4}

시간 복잡도: O((V + E) log V) 공간 복잡도: O(V)


문제 18: Network Delay Time (LeetCode 743)

def networkDelayTime(times: list[list[int]], n: int, k: int) -> int:
    graph = defaultdict(list)
    for u, v, w in times:
        graph[u].append((v, w))

    distances = {i: float('inf') for i in range(1, n + 1)}
    distances[k] = 0
    pq = [(0, k)]

    while pq:
        dist, node = heapq.heappop(pq)

        if dist > distances[node]:
            continue

        for neighbor, weight in graph[node]:
            new_dist = dist + weight
            if new_dist < distances[neighbor]:
                distances[neighbor] = new_dist
                heapq.heappush(pq, (new_dist, neighbor))

    max_dist = max(distances.values())
    return max_dist if max_dist != float('inf') else -1

# 예시
# times = [[2,1,1],[2,3,1],[3,4,1]], n=4, k=2
# Output: 2

문제 19: Cheapest Flights Within K Stops (LeetCode 787)

Bellman-Ford 변형으로 k번 이하 경유지로 최저 비용을 구합니다.

def findCheapestPrice(n: int, flights: list[list[int]], src: int, dst: int, k: int) -> int:
    # Bellman-Ford: k+1번 릴랙세이션
    prices = [float('inf')] * n
    prices[src] = 0

    for i in range(k + 1):
        temp = prices.copy()  # 현재 단계 결과를 분리

        for u, v, w in flights:
            if prices[u] != float('inf') and prices[u] + w < temp[v]:
                temp[v] = prices[u] + w

        prices = temp

    return prices[dst] if prices[dst] != float('inf') else -1

# 예시
# n=3, flights=[[0,1,100],[1,2,100],[0,2,500]], src=0, dst=2, k=1
# Output: 200 (0→1→2)

6. FAANG 기업별 트리/그래프 출제 경향

Google

Google은 그래프 문제를 매우 자주 출제합니다.

  • 자주 출제: 그래프 탐색, 위상 정렬, 최단 경로
  • 핵심: 효율적인 알고리즘 선택과 복잡도 분석
  • 예시 문제: Course Schedule II, Word Ladder, Alien Dictionary

Meta (Facebook)

Meta는 Tree Traversal과 LCA 문제를 선호합니다.

  • 자주 출제: Binary Tree, LCA, Serialize/Deserialize
  • 핵심: 재귀와 반복 두 방식 모두 구현 가능
  • 예시 문제: Binary Tree Right Side View, Vertical Order Traversal

Amazon

Amazon은 BFS/최단 경로 문제를 많이 출제합니다.

  • 자주 출제: Number of Islands, BFS 변형, Union Find
  • 핵심: 실용적이고 효율적인 해법
  • 예시 문제: Rotting Oranges, Find if Path Exists in Graph

7. 연습 문제 퀴즈

퀴즈 1: 이진 트리의 지름 (Diameter)

이진 트리에서 임의의 두 노드 사이의 최장 경로 길이(간선 수)를 구하라. 경로가 루트를 통과하지 않아도 됩니다.

정답: DFS로 각 노드에서 왼쪽/오른쪽 최대 깊이의 합을 계산하고 전역 최대값을 업데이트합니다.

설명:

def diameterOfBinaryTree(root) -> int:
    max_diameter = [0]

    def depth(node):
        if not node:
            return 0
        left_depth = depth(node.left)
        right_depth = depth(node.right)
        # 현재 노드를 지나는 경로의 지름
        max_diameter[0] = max(max_diameter[0], left_depth + right_depth)
        return 1 + max(left_depth, right_depth)

    depth(root)
    return max_diameter[0]
퀴즈 2: 그래프에서 사이클 감지

방향 그래프 graph = {0: [1], 1: [2], 2: [0], 3: [4], 4: []}에서 사이클이 있는가?

정답: Yes (0 → 1 → 2 → 0)

설명: DFS와 색상 마킹(0=미방문, 1=방문중, 2=완료)을 사용합니다. 방문 중인 노드를 다시 만나면 사이클입니다.

def hasCycle(graph):
    state = {}

    def dfs(node):
        if state.get(node) == 1:
            return True   # 사이클
        if state.get(node) == 2:
            return False  # 이미 처리

        state[node] = 1
        for neighbor in graph.get(node, []):
            if dfs(neighbor):
                return True
        state[node] = 2
        return False

    return any(dfs(node) for node in graph if node not in state)
퀴즈 3: BST에서 두 노드 값의 합 찾기

BST와 타겟 k가 주어질 때 두 노드 값의 합이 k가 되는 경우가 있는지 판별하라.

정답: Inorder 순회로 정렬된 값들을 얻은 후 Two Pointers를 적용합니다.

설명:

def findTarget(root, k: int) -> bool:
    def inorder(node):
        if not node:
            return []
        return inorder(node.left) + [node.val] + inorder(node.right)

    nums = inorder(root)
    left, right = 0, len(nums) - 1

    while left < right:
        total = nums[left] + nums[right]
        if total == k:
            return True
        elif total < k:
            left += 1
        else:
            right -= 1
    return False
퀴즈 4: 섬의 최대 넓이

이진 그리드에서 1로 연결된 섬 중 가장 큰 섬의 넓이를 구하라.

정답: DFS/BFS로 각 섬의 넓이를 계산하고 최대값을 반환합니다.

설명:

def maxAreaOfIsland(grid):
    rows, cols = len(grid), len(grid[0])

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
            return 0
        grid[r][c] = 0  # 방문 표시
        return 1 + dfs(r+1,c) + dfs(r-1,c) + dfs(r,c+1) + dfs(r,c-1)

    return max(dfs(r, c) for r in range(rows) for c in range(cols) if grid[r][c])
퀴즈 5: 최소 스패닝 트리 - Kruskal 알고리즘

Union Find를 이용해 n개의 도시를 최소 비용으로 연결하는 알고리즘을 구현하라. (Min Cost to Connect All Points, LeetCode 1584)

정답: 모든 간선을 비용 오름차순으로 정렬 후, Union Find로 사이클 없이 간선을 추가합니다.

설명:

def minCostConnectPoints(points):
    n = len(points)
    # 모든 간선 생성 (맨해튼 거리)
    edges = []
    for i in range(n):
        for j in range(i + 1, n):
            dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])
            edges.append((dist, i, j))

    edges.sort()
    uf = UnionFind(n)
    total_cost = 0
    edges_used = 0

    for cost, u, v in edges:
        if uf.union(u, v):
            total_cost += cost
            edges_used += 1
            if edges_used == n - 1:
                break

    return total_cost

마무리

트리와 그래프는 FAANG 인터뷰에서 가장 다양한 패턴이 등장하는 분야입니다. 특히:

  • DFS/BFS 선택: 최단 경로 → BFS, 모든 경로 탐색 → DFS
  • 트리 문제: 재귀가 자연스러운 경우가 많음, 반복도 준비
  • 그래프 문제: visited 처리를 항상 명확히 하기
  • Union Find: 연결성 문제에서 강력한 도구

추천 LeetCode 목록:

  • Easy: Maximum Depth, Invert Tree, Symmetric Tree
  • Medium: Course Schedule, Number of Islands, LCA, BFS/DFS 변형
  • Hard: Serialize/Deserialize, Word Ladder II, Alien Dictionary

FAANG Coding Interview Mastery: Trees & Graphs Core Patterns

FAANG Coding Interview Mastery: Trees & Graphs Core Patterns

Trees and graphs rank second only to arrays and strings in FAANG interview frequency. Google and Meta in particular place heavy emphasis on graph and tree traversal. This guide covers 5 essential patterns with complete, working Python implementations.


1. Binary Tree Core Patterns

DFS explores a tree by going as deep as possible before backtracking. It can be implemented recursively or iteratively with a stack.

Tree Node Definition

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

Three DFS Traversals (Recursive)

# Inorder: Left → Current → Right (sorted order in BST)
def inorder(root):
    if not root:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

# Preorder: Current → Left → Right (tree copying, serialization)
def preorder(root):
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

# Postorder: Left → Right → Current (directory size calculation)
def postorder(root):
    if not root:
        return []
    return postorder(root.left) + postorder(root.right) + [root.val]

Iterative DFS (with explicit stack)

def inorderIterative(root):
    result = []
    stack = []
    current = root

    while current or stack:
        # Go as far left as possible
        while current:
            stack.append(current)
            current = current.left

        current = stack.pop()
        result.append(current.val)
        current = current.right

    return result

def preorderIterative(root):
    if not root:
        return []

    result = []
    stack = [root]

    while stack:
        node = stack.pop()
        result.append(node.val)

        # Push right first so left is processed first
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

    return result

BFS (Breadth-First Search) — Level Order Traversal

from collections import deque

def levelOrder(root):
    if not root:
        return []

    result = []
    queue = deque([root])

    while queue:
        level_size = len(queue)
        current_level = []

        for _ in range(level_size):
            node = queue.popleft()
            current_level.append(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(current_level)

    return result

# Example:
#     Tree:   3
#            / \
#           9  20
#              / \
#             15   7
# Output: [[3], [9,20], [15,7]]

Problem 1: Maximum Depth of Binary Tree (LeetCode 104)

def maxDepth(root) -> int:
    if not root:
        return 0
    return 1 + max(maxDepth(root.left), maxDepth(root.right))

# BFS approach
def maxDepthBFS(root) -> int:
    if not root:
        return 0

    depth = 0
    queue = deque([root])

    while queue:
        depth += 1
        for _ in range(len(queue)):
            node = queue.popleft()
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

    return depth

Time Complexity: O(n) — every node visited once Space Complexity: O(h) — h is tree height; O(n) worst case for skewed trees


Problem 2: Invert Binary Tree (LeetCode 226)

def invertTree(root):
    if not root:
        return None

    root.left, root.right = invertTree(root.right), invertTree(root.left)
    return root

# Iterative approach (BFS)
def invertTreeBFS(root):
    if not root:
        return None

    queue = deque([root])
    while queue:
        node = queue.popleft()
        node.left, node.right = node.right, node.left

        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

    return root

Time Complexity: O(n) Space Complexity: O(n)


Problem 3: Symmetric Tree (LeetCode 101)

Check whether a binary tree is a mirror of itself.

def isSymmetric(root) -> bool:
    def isMirror(left, right):
        if not left and not right:
            return True
        if not left or not right:
            return False
        return (left.val == right.val and
                isMirror(left.left, right.right) and
                isMirror(left.right, right.left))

    return isMirror(root.left, root.right)

# Iterative approach
def isSymmetricIterative(root) -> bool:
    queue = deque([(root.left, root.right)])

    while queue:
        left, right = queue.popleft()

        if not left and not right:
            continue
        if not left or not right:
            return False
        if left.val != right.val:
            return False

        queue.append((left.left, right.right))
        queue.append((left.right, right.left))

    return True

Problem 4: Path Sum II (LeetCode 113)

Find all root-to-leaf paths where the sum equals targetSum.

def pathSum(root, targetSum: int):
    result = []

    def dfs(node, current_path, remaining):
        if not node:
            return

        current_path.append(node.val)
        remaining -= node.val

        # Leaf node with correct sum
        if not node.left and not node.right and remaining == 0:
            result.append(list(current_path))
        else:
            dfs(node.left, current_path, remaining)
            dfs(node.right, current_path, remaining)

        # Backtrack
        current_path.pop()

    dfs(root, [], targetSum)
    return result

Problem 5: Lowest Common Ancestor (LeetCode 236)

Find the LCA of two nodes p and q in a binary tree.

def lowestCommonAncestor(root, p, q):
    if not root or root == p or root == q:
        return root

    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)

    # Found on both sides → current node is LCA
    if left and right:
        return root
    # Found on one side only → that side is the LCA
    return left if left else right

# Example:
#      3
#     / \
#    5   1
#   / \ / \
#  6  2 0  8
#    / \
#   7   4
# p=5, q=1 → LCA = 3
# p=5, q=4 → LCA = 5

Problem 6: Serialize and Deserialize Binary Tree (LeetCode 297) — Hard

class Codec:
    def serialize(self, root) -> str:
        """Serialize tree to string using BFS"""
        if not root:
            return ""

        result = []
        queue = deque([root])

        while queue:
            node = queue.popleft()
            if node:
                result.append(str(node.val))
                queue.append(node.left)
                queue.append(node.right)
            else:
                result.append("null")

        return ",".join(result)

    def deserialize(self, data: str):
        """Reconstruct tree from serialized string"""
        if not data:
            return None

        values = data.split(",")
        root = TreeNode(int(values[0]))
        queue = deque([root])
        i = 1

        while queue:
            node = queue.popleft()

            if i < len(values) and values[i] != "null":
                node.left = TreeNode(int(values[i]))
                queue.append(node.left)
            i += 1

            if i < len(values) and values[i] != "null":
                node.right = TreeNode(int(values[i]))
                queue.append(node.right)
            i += 1

        return root

Time Complexity: O(n) for both operations Space Complexity: O(n)


2. Binary Search Tree (BST) Patterns

BST Properties

  • All values in the left subtree are less than the current node
  • All values in the right subtree are greater than the current node
  • Inorder traversal visits nodes in sorted order

Problem 7: Validate Binary Search Tree (LeetCode 98)

def isValidBST(root) -> bool:
    def validate(node, min_val, max_val):
        if not node:
            return True

        if node.val <= min_val or node.val >= max_val:
            return False

        return (validate(node.left, min_val, node.val) and
                validate(node.right, node.val, max_val))

    return validate(root, float('-inf'), float('inf'))

# Common pitfall: simply checking left.val < root.val < right.val is WRONG
# Example of invalid BST that passes the naive check:
#     5
#    / \
#   1   4
#      / \
#     3   6
# root.right.left = 3 (violates BST: 3 < 5 should not appear in right subtree)

Problem 8: Kth Smallest Element in BST (LeetCode 230)

def kthSmallest(root, k: int) -> int:
    count = [0]
    result = [0]

    def inorder(node):
        if not node or count[0] >= k:
            return
        inorder(node.left)
        count[0] += 1
        if count[0] == k:
            result[0] = node.val
            return
        inorder(node.right)

    inorder(root)
    return result[0]

# Iterative approach (more explicit)
def kthSmallestIterative(root, k: int) -> int:
    stack = []
    current = root

    while current or stack:
        while current:
            stack.append(current)
            current = current.left

        current = stack.pop()
        k -= 1
        if k == 0:
            return current.val
        current = current.right

    return -1

Problem 9: Convert Sorted Array to BST (LeetCode 108)

Convert a sorted array to a height-balanced BST.

def sortedArrayToBST(nums: list[int]):
    def buildBST(left, right):
        if left > right:
            return None

        # Always pick the middle element as root to ensure balance
        mid = left + (right - left) // 2
        root = TreeNode(nums[mid])
        root.left = buildBST(left, mid - 1)
        root.right = buildBST(mid + 1, right)
        return root

    return buildBST(0, len(nums) - 1)

3. Graph BFS/DFS Patterns

Graph Representations

# Adjacency list (preferred)
graph = {
    0: [1, 2],
    1: [0, 3],
    2: [0, 4],
    3: [1],
    4: [2]
}

# Adjacency matrix
matrix = [
    [0, 1, 1, 0, 0],
    [1, 0, 0, 1, 0],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 0, 0],
    [0, 0, 1, 0, 0]
]

Standard Templates

# DFS (recursive)
def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()
    visited.add(start)
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

# BFS
def bfs(graph, start):
    visited = {start}
    queue = deque([start])
    while queue:
        node = queue.popleft()
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

Problem 10: Number of Islands (LeetCode 200)

def numIslands(grid: list[list[str]]) -> int:
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])
    count = 0

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == '0':
            return
        grid[r][c] = '0'  # Mark as visited
        dfs(r + 1, c)
        dfs(r - 1, c)
        dfs(r, c + 1)
        dfs(r, c - 1)

    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == '1':
                count += 1
                dfs(r, c)

    return count

# Example:
# grid = [
#   ["1","1","0","0","0"],
#   ["1","1","0","0","0"],
#   ["0","0","1","0","0"],
#   ["0","0","0","1","1"]
# ]
# Output: 3

Time Complexity: O(mn) Space Complexity: O(mn) for recursion stack


Problem 11: Clone Graph (LeetCode 133)

class Node:
    def __init__(self, val=0, neighbors=None):
        self.val = val
        self.neighbors = neighbors if neighbors is not None else []

def cloneGraph(node):
    if not node:
        return None

    cloned = {}  # Maps original node to its clone

    def dfs(original):
        if original in cloned:
            return cloned[original]

        copy = Node(original.val)
        cloned[original] = copy

        for neighbor in original.neighbors:
            copy.neighbors.append(dfs(neighbor))

        return copy

    return dfs(node)

Time Complexity: O(V + E) Space Complexity: O(V)


Problem 12: Course Schedule (LeetCode 207) — Topological Sort

Determine if you can finish all courses given prerequisite relationships.

from collections import defaultdict, deque

def canFinish(numCourses: int, prerequisites: list[list[int]]) -> bool:
    graph = defaultdict(list)
    in_degree = [0] * numCourses

    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1

    # Start with courses that have no prerequisites
    queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
    completed = 0

    while queue:
        course = queue.popleft()
        completed += 1

        for next_course in graph[course]:
            in_degree[next_course] -= 1
            if in_degree[next_course] == 0:
                queue.append(next_course)

    return completed == numCourses

# DFS cycle detection
def canFinishDFS(numCourses: int, prerequisites: list[list[int]]) -> bool:
    graph = defaultdict(list)
    for course, prereq in prerequisites:
        graph[prereq].append(course)

    # States: 0 = unvisited, 1 = visiting, 2 = done
    state = [0] * numCourses

    def hasCycle(node):
        if state[node] == 1:  # Cycle detected
            return True
        if state[node] == 2:  # Already processed
            return False

        state[node] = 1
        for neighbor in graph[node]:
            if hasCycle(neighbor):
                return True
        state[node] = 2
        return False

    for i in range(numCourses):
        if hasCycle(i):
            return False
    return True

Problem 13: Pacific Atlantic Water Flow (LeetCode 417)

def pacificAtlantic(heights: list[list[int]]) -> list[list[int]]:
    if not heights:
        return []

    rows, cols = len(heights), len(heights[0])
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

    def bfs(starts):
        visited = set(starts)
        queue = deque(starts)

        while queue:
            r, c = queue.popleft()
            for dr, dc in directions:
                nr, nc = r + dr, c + dc
                if (0 <= nr < rows and 0 <= nc < cols and
                    (nr, nc) not in visited and
                    heights[nr][nc] >= heights[r][c]):  # Reverse flow
                    visited.add((nr, nc))
                    queue.append((nr, nc))
        return visited

    # Pacific: top row + left column
    pacific_starts = [(0, c) for c in range(cols)] + [(r, 0) for r in range(1, rows)]
    # Atlantic: bottom row + right column
    atlantic_starts = [(rows-1, c) for c in range(cols)] + [(r, cols-1) for r in range(rows-1)]

    pacific = bfs(pacific_starts)
    atlantic = bfs(atlantic_starts)

    return [[r, c] for r, c in pacific & atlantic]

Problem 14: Word Ladder (LeetCode 127)

Find the shortest transformation sequence from beginWord to endWord.

def ladderLength(beginWord: str, endWord: str, wordList: list[str]) -> int:
    word_set = set(wordList)
    if endWord not in word_set:
        return 0

    queue = deque([(beginWord, 1)])
    visited = {beginWord}

    while queue:
        word, steps = queue.popleft()

        for i in range(len(word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                new_word = word[:i] + c + word[i+1:]

                if new_word == endWord:
                    return steps + 1

                if new_word in word_set and new_word not in visited:
                    visited.add(new_word)
                    queue.append((new_word, steps + 1))

    return 0

# Example:
# beginWord = "hit", endWord = "cog"
# wordList = ["hot","dot","dog","lot","log","cog"]
# Output: 5 (hit -> hot -> dot -> dog -> cog)

Time Complexity: O(M^2 _ N) — M is word length, N is dictionary size Space Complexity: O(M^2 _ N)


4. Union Find (Disjoint Set) Pattern

Implementation

class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        # Path Compression
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py:
            return False  # Already in same set

        # Union by Rank
        if self.rank[px] < self.rank[py]:
            px, py = py, px
        self.parent[py] = px
        if self.rank[px] == self.rank[py]:
            self.rank[px] += 1
        return True

    def connected(self, x, y):
        return self.find(x) == self.find(y)

Problem 15: Number of Connected Components (LeetCode 323)

def countComponents(n: int, edges: list[list[int]]) -> int:
    uf = UnionFind(n)
    components = n

    for u, v in edges:
        if uf.union(u, v):
            components -= 1  # Two components merged

    return components

Problem 16: Redundant Connection (LeetCode 684)

Find the edge that, when removed, results in a tree.

def findRedundantConnection(edges: list[list[int]]) -> list[int]:
    n = len(edges)
    uf = UnionFind(n + 1)

    for u, v in edges:
        if not uf.union(u, v):
            return [u, v]  # Already connected → redundant edge

    return []

Problem 17: Accounts Merge (LeetCode 721)

def accountsMerge(accounts: list[list[str]]) -> list[list[str]]:
    email_to_id = {}
    email_to_name = {}

    for i, account in enumerate(accounts):
        name = account[0]
        for email in account[1:]:
            if email not in email_to_id:
                email_to_id[email] = len(email_to_id)
            email_to_name[email] = name

    uf = UnionFind(len(email_to_id))

    for account in accounts:
        first_email = account[1]
        first_id = email_to_id[first_email]
        for email in account[2:]:
            uf.union(first_id, email_to_id[email])

    from collections import defaultdict
    groups = defaultdict(list)
    for email, eid in email_to_id.items():
        root = uf.find(eid)
        groups[root].append(email)

    result = []
    for root, emails in groups.items():
        name = email_to_name[emails[0]]
        result.append([name] + sorted(emails))

    return result

5. Shortest Path Algorithms

Dijkstra's Algorithm

Finds single-source shortest paths in graphs with non-negative edge weights.

import heapq
from collections import defaultdict

def dijkstra(graph: dict, start: int) -> dict:
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    pq = [(0, start)]  # (distance, node)

    while pq:
        current_dist, current_node = heapq.heappop(pq)

        # Skip if a shorter path has already been found
        if current_dist > distances[current_node]:
            continue

        for neighbor, weight in graph[current_node]:
            distance = current_dist + weight

            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))

    return distances

Time Complexity: O((V + E) log V) Space Complexity: O(V)


Problem 18: Network Delay Time (LeetCode 743)

def networkDelayTime(times: list[list[int]], n: int, k: int) -> int:
    graph = defaultdict(list)
    for u, v, w in times:
        graph[u].append((v, w))

    distances = {i: float('inf') for i in range(1, n + 1)}
    distances[k] = 0
    pq = [(0, k)]

    while pq:
        dist, node = heapq.heappop(pq)

        if dist > distances[node]:
            continue

        for neighbor, weight in graph[node]:
            new_dist = dist + weight
            if new_dist < distances[neighbor]:
                distances[neighbor] = new_dist
                heapq.heappush(pq, (new_dist, neighbor))

    max_dist = max(distances.values())
    return max_dist if max_dist != float('inf') else -1

# Example:
# times = [[2,1,1],[2,3,1],[3,4,1]], n=4, k=2
# Output: 2

Problem 19: Cheapest Flights Within K Stops (LeetCode 787)

Bellman-Ford variant with at most k intermediate stops.

def findCheapestPrice(n: int, flights: list[list[int]], src: int, dst: int, k: int) -> int:
    # Bellman-Ford: relax k+1 times
    prices = [float('inf')] * n
    prices[src] = 0

    for i in range(k + 1):
        temp = prices.copy()  # Isolate each iteration's updates

        for u, v, w in flights:
            if prices[u] != float('inf') and prices[u] + w < temp[v]:
                temp[v] = prices[u] + w

        prices = temp

    return prices[dst] if prices[dst] != float('inf') else -1

# Example:
# n=3, flights=[[0,1,100],[1,2,100],[0,2,500]], src=0, dst=2, k=1
# Output: 200 (0 -> 1 -> 2)

Google

Google is particularly heavy on graph problems.

  • Frequent: Graph traversal, topological sort, shortest path
  • Key skills: Choosing the right algorithm and justifying complexity
  • Example problems: Course Schedule II, Word Ladder, Alien Dictionary

Meta (Facebook)

Meta favors Tree Traversal and LCA problems.

  • Frequent: Binary Tree, LCA, Serialize/Deserialize
  • Key skills: Comfortable with both recursive and iterative implementations
  • Example problems: Binary Tree Right Side View, Vertical Order Traversal

Amazon

Amazon leans toward BFS and shortest path problems.

  • Frequent: Number of Islands, BFS variants, Union Find
  • Key skills: Practical and efficient solutions
  • Example problems: Rotting Oranges, Find if Path Exists in Graph

7. Practice Quiz

Quiz 1: Diameter of Binary Tree

Find the length of the longest path between any two nodes in a binary tree. The path does not need to pass through the root.

Answer: Use DFS; at each node compute the sum of left and right max depths and update a global maximum.

Explanation:

def diameterOfBinaryTree(root) -> int:
    max_diameter = [0]

    def depth(node):
        if not node:
            return 0
        left_depth = depth(node.left)
        right_depth = depth(node.right)
        # Path through current node
        max_diameter[0] = max(max_diameter[0], left_depth + right_depth)
        return 1 + max(left_depth, right_depth)

    depth(root)
    return max_diameter[0]
Quiz 2: Detect Cycle in a Directed Graph

Graph {0: [1], 1: [2], 2: [0], 3: [4], 4: []} — does it contain a cycle?

Answer: Yes (0 -> 1 -> 2 -> 0)

Explanation: Use DFS with three-color marking (0=unvisited, 1=visiting, 2=done). If you encounter a node currently being visited, a cycle exists.

def hasCycle(graph):
    state = {}

    def dfs(node):
        if state.get(node) == 1:
            return True
        if state.get(node) == 2:
            return False

        state[node] = 1
        for neighbor in graph.get(node, []):
            if dfs(neighbor):
                return True
        state[node] = 2
        return False

    return any(dfs(node) for node in graph if node not in state)
Quiz 3: Two Sum in a BST

Given a BST and target k, determine if there exist two nodes whose values sum to k.

Answer: Perform an inorder traversal to get a sorted list, then apply Two Pointers.

Explanation:

def findTarget(root, k: int) -> bool:
    def inorder(node):
        if not node:
            return []
        return inorder(node.left) + [node.val] + inorder(node.right)

    nums = inorder(root)
    left, right = 0, len(nums) - 1

    while left < right:
        total = nums[left] + nums[right]
        if total == k:
            return True
        elif total < k:
            left += 1
        else:
            right -= 1
    return False
Quiz 4: Max Area of Island

In a binary grid, find the maximum area of a connected island of 1s.

Answer: DFS/BFS each island and count cells; return the maximum.

Explanation:

def maxAreaOfIsland(grid):
    rows, cols = len(grid), len(grid[0])

    def dfs(r, c):
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] == 0:
            return 0
        grid[r][c] = 0  # Mark visited
        return 1 + dfs(r+1,c) + dfs(r-1,c) + dfs(r,c+1) + dfs(r,c-1)

    return max(dfs(r, c) for r in range(rows) for c in range(cols) if grid[r][c])
Quiz 5: Minimum Spanning Tree — Kruskal's Algorithm

Use Union Find to connect n cities with minimum total cost. (Min Cost to Connect All Points, LeetCode 1584)

Answer: Sort all edges by cost, then greedily add edges using Union Find to avoid cycles.

Explanation:

def minCostConnectPoints(points):
    n = len(points)
    edges = []
    for i in range(n):
        for j in range(i + 1, n):
            dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])
            edges.append((dist, i, j))

    edges.sort()
    uf = UnionFind(n)
    total_cost = 0
    edges_used = 0

    for cost, u, v in edges:
        if uf.union(u, v):
            total_cost += cost
            edges_used += 1
            if edges_used == n - 1:
                break

    return total_cost

Conclusion

Trees and graphs offer the widest variety of patterns in FAANG interviews. Key decision rules:

  • BFS vs DFS: Use BFS for shortest path, DFS for all-path exploration
  • Tree problems: Recursion is often elegant; prepare iterative versions too
  • Graph problems: Always manage the visited set carefully
  • Union Find: A powerful tool for any connectivity problem

Recommended LeetCode problems:

  • Easy: Maximum Depth, Invert Tree, Symmetric Tree
  • Medium: Course Schedule, Number of Islands, LCA, BFS/DFS variants
  • Hard: Serialize/Deserialize, Word Ladder II, Alien Dictionary