Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python으로 이진 트리의 최소 공통 조상(LCA) 찾는 방법

이진 트리의 최소 공통 조상(LCA)이란?

이진 트리가 주어졌을 때, 두 노드의 최소 공통 조상(Lowest Common Ancestor, LCA)을 찾는 문제를 살펴보겠습니다. 두 노드 p와 q의 LCA란, p와 q를 모두 자손(descendant)으로 가지면서 트리에서 가장 아래쪽에 위치한 노드를 의미합니다.

예를 들어 이진 트리가 [3,5,1,6,2,0,8,null,null,7,4]로 구성되어 있다면 트리는 다음과 같은 형태가 됩니다.

Python으로 이진 트리의 최소 공통 조상(LCA) 찾는 방법

위 트리에서 5와 1의 LCA는 3입니다.

문제 해결 접근 방법

이 문제는 재귀 호출을 활용하면 깔끔하게 해결할 수 있습니다. 알고리즘의 핵심 단계는 다음과 같습니다.

  • 트리(현재 노드)가 비어 있다면 null을 반환합니다.
  • 현재 루트 노드의 값이 p 또는 q와 같다면 해당 루트를 반환합니다.
  • left := 왼쪽 서브트리에서 p와 q의 LCA를 재귀적으로 구합니다.
  • right := 오른쪽 서브트리에서 p와 q의 LCA를 재귀적으로 구합니다.
  • left와 right가 모두 null이 아니라면, p와 q가 서로 다른 서브트리에 분포해 있다는 뜻이므로 현재 루트가 LCA입니다. 따라서 루트를 반환합니다.
  • 그렇지 않다면 left와 right 중 null이 아닌 쪽을 그대로 반환합니다.

핵심 아이디어는 간단합니다. 어떤 노드를 기준으로 왼쪽과 오른쪽 양쪽에서 각각 대상 노드가 발견된다면, 그 노드가 바로 두 노드가 만나는 지점, 즉 최소 공통 조상이라는 것입니다.

Python 구현 예제

아래 코드는 위 알고리즘을 실제로 구현한 것입니다. 트리 생성을 위한 헬퍼 함수와 함께 LCA를 구하는 Solution 클래스를 포함하고 있습니다.

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

def insert(temp, data):
    que = []
    que.append(temp)
    while len(que):
        temp = que[0]
        que.pop(0)
        if not temp.left:
            if data is not None:
                temp.left = TreeNode(data)
            else:
                temp.left = TreeNode(0)
            break
        else:
            que.append(temp.left)
            if not temp.right:
                if data is not None:
                    temp.right = TreeNode(data)
                else:
                    temp.right = TreeNode(0)
                break
            else:
                que.append(temp.right)

def make_tree(elements):
    Tree = TreeNode(elements[0])
    for element in elements[1:]:
        insert(Tree, element)
    return Tree

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        if not root:
            return None
        if root.data == p or root.data == q:
            return root
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if right and left:
            return root
        return right or left

ob1 = Solution()
tree = make_tree([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])
print(ob1.lowestCommonAncestor(tree, 5, 1).data)

입력

[3,5,1,6,2,0,8,null,null,7,4]
5
1

출력

3

시간 복잡도 및 특징 정리

  • 시간 복잡도: O(n) — 트리의 모든 노드를 최악의 경우 한 번씩 방문합니다.
  • 공간 복잡도: O(h) — 재귀 호출 스택의 깊이는 트리의 높이 h에 비례합니다. 균형 잡힌 트리라면 O(log n)입니다.

이 알고리즘은 이진 탐색 트리(BST)가 아닌 일반 이진 트리에서도 동작하며, 노드 값의 순서나 중복 여부에 제약받지 않는다는 장점이 있습니다. 재귀적 후위 순회(post-order traversal)의 전형적인 응용 사례이므로, 코딩 인터뷰에서도 자주 출제되는 유형입니다.