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

Python으로 좌우 서브트리가 동일한 가장 큰 서브트리 찾는 방법


문제 소개

하나의 이진 트리(binary tree)가 주어졌을 때, 왼쪽 서브트리와 오른쪽 서브트리가 완전히 동일한 가장 큰 서브트리를 찾는 문제입니다. 이때 선호되는 시간 복잡도는 O(n)입니다.

예를 들어 아래와 같은 트리가 입력으로 주어진다면,

Python으로 좌우 서브트리가 동일한 가장 큰 서브트리 찾는 방법

결과는 다음과 같이 나타납니다.

Python으로 좌우 서브트리가 동일한 가장 큰 서브트리 찾는 방법

접근 방법

이 문제는 후위 순회(postorder traversal)를 활용한 재귀적 방식으로 해결할 수 있습니다. 핵심 아이디어는 다음과 같습니다.

  • 각 노드를 기준으로 서브트리의 구조와 값을 문자열로 직렬화(인코딩)합니다.
  • 왼쪽 서브트리의 인코딩 결과와 오른쪽 서브트리의 인코딩 결과가 일치하면, 해당 노드를 루트로 하는 서브트리는 좌우가 동일한 조건을 만족합니다.
  • 조건을 만족하는 서브트리 중 크기가 가장 큰 것을 계속 추적하여 마지막에 반환합니다.

알고리즘 단계

solve() 함수를 정의합니다. 매개변수는 root, encode, maxSize, maxNode입니다.

  1. root가 None이면 0을 반환합니다.
  2. left_list와 right_list를 빈 문자열을 담은 리스트로 초기화합니다.
  3. ls := solve(root.left, left_list, maxSize, maxNode)로 왼쪽 서브트리의 크기를 구하고, rs := solve(root.right, right_list, maxSize, maxNode)로 오른쪽 서브트리의 크기를 구합니다.
  4. size := ls + rs + 1을 계산합니다.
  5. left_list[0]과 right_list[0]이 같다면, 즉 좌우 서브트리가 동일하다면 size가 maxSize[0]보다 클 때 maxSize[0] := size, maxNode[0] := root로 갱신합니다.
  6. encode[0]에 "|" 구분자와 함께 왼쪽 서브트리 인코딩, 현재 노드의 값, 오른쪽 서브트리 인코딩을 차례로 이어 붙입니다.
  7. size를 반환합니다.

메인 메서드에서는 maximum := [0], encode := 빈 문자열을 담은 리스트를 준비한 뒤 solve(node, encode, maximum, maxNode)를 호출하고, 최종적으로 maximum을 반환합니다.

구현 예제

아래 구현을 통해 더 잘 이해해 보겠습니다.

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

def solve(root, encode, maxSize, maxNode):
    if (root == None):
        return 0
    left_list = [""]
    right_list = [""]
    ls = solve(root.left, left_list, maxSize, maxNode)
    rs = solve(root.right, right_list, maxSize, maxNode)
    size = ls + rs + 1
    if (left_list[0] == right_list[0]):
        if (size > maxSize[0]):
            maxSize[0] = size
            maxNode[0] = root
    encode[0] = encode[0] + "|" + left_list[0] + "|"
    encode[0] = encode[0] + "|" + str(root.data) + "|"
    encode[0] = encode[0] + "|" + right_list[0] + "|"
    return size

def largestSubtree(node, maxNode):
    maximum = [0]
    encode = [""]
    solve(node, encode, maximum, maxNode)
    return maximum

root = TreeNode(55)
root.left = TreeNode(15)
root.right = TreeNode(70)
root.left.left = TreeNode(10)
root.left.right = TreeNode(25)
root.right.left = TreeNode(75)
root.right.left.left = TreeNode(65)
root.right.left.right = TreeNode(80)
root.right.right = TreeNode(75)
root.right.right.left = TreeNode(65)
root.right.right.right = TreeNode(80)

maxNode = [None]
maximum = largestSubtree(root, maxNode)
print("Root of largest sub-tree", maxNode[0].data)
print("and its size is", maximum)

입력

root = TreeNode(55)
root.left = TreeNode(15)
root.right = TreeNode(70)
root.left.left = TreeNode(10)
root.left.right = TreeNode(25)
root.right.left = TreeNode(75)
root.right.left.left = TreeNode(65)
root.right.left.right = TreeNode(80)
root.right.right = TreeNode(75)
root.right.right.left = TreeNode(65)
root.right.right.right = TreeNode(80)

실행 결과

Root of largest sub-tree 70
and its size is [7]

즉, 값 70을 루트로 하는 서브트리가 왼쪽 서브트리(75 → 65, 80)와 오른쪽 서브트리(75 → 65, 80)가 동일한 크기 7의 가장 큰 서브트리임을 확인할 수 있습니다.

시간 복잡도 참고 사항

위와 같은 문자열 연결 기반의 직렬화 방식은 구현이 직관적이라는 장점이 있지만, 최악의 경우 문자열 비교와 결합 비용 때문에 O(n²)까지 늘어날 수 있습니다. 엄격하게 O(n)의 성능을 보장하려면 각 서브트리에 고유 ID를 부여하고 해시 맵(hash map)으로 관리하는 방식을 사용하는 것이 좋습니다.