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

파이썬으로 이진 트리의 리프에서 시작하는 사전순 최소 문자열 구하기

이진 트리의 루트 노드가 주어졌을 때, 각 노드는 0부터 25까지의 값을 가지며 이 값들은 알파벳 소문자 'a'부터 'z'에 대응됩니다. 즉, 값 0은 'a', 값 1은 'b'와 같은 식으로 매핑됩니다. 우리가 찾아야 하는 것은 트리의 리프(leaf) 노드에서 시작하여 루트에서 끝나는 문자열 중 사전순(lexicographically)으로 가장 작은 문자열입니다.

예를 들어 다음과 같은 트리가 있다고 가정해 보겠습니다.

파이썬으로 이진 트리의 리프에서 시작하는 사전순 최소 문자열 구하기

이 경우 루트까지의 경로는 [0, 3, 25]이며, 이를 역순으로 읽으면 'a', 'd', 'z'가 되므로 출력 결과는 "adz"입니다.

문제 해결 접근 방식

이 문제는 깊이 우선 탐색(DFS)을 활용하면 효율적으로 해결할 수 있습니다. 핵심 아이디어는 루트에서 리프까지 경로를 따라 내려가면서 방문한 노드의 문자를 누적하고, 리프에 도달하는 순간 지금까지 쌓인 경로를 역순으로 만들어 정답과 비교하는 것입니다.

알고리즘 단계

  • DFS 순회 메서드를 다음과 같이 정의합니다.

  • 노드가 null이 아니라면,

    • 노드의 값을 해당하는 문자로 변환하여 배열 A에 추가합니다.

    • 노드에 왼쪽과 오른쪽 자식이 모두 없다면(즉, 리프 노드라면),

      • 배열 A의 요소들을 역순으로 연결한 문자열과 현재 정답(ans)을 비교하여 더 작은 값을 ans에 저장합니다.

      • 배열 A에서 마지막 요소를 제거합니다.

      • 함수를 종료하고 반환합니다.

    • 왼쪽 자식 노드로 dfs(node.left, A)를 재귀 호출합니다.

    • 오른쪽 자식 노드로 dfs(node.right, A)를 재귀 호출합니다.

    • 재귀 호출이 끝나면 배열 A에서 마지막 요소를 제거합니다(백트래킹).

    • 반환합니다.

  • 실제 메서드는 다음과 같이 동작합니다.

  • ans := "~" 로 초기화합니다. ('~'의 ASCII 코드는 소문자보다 크므로 초기 비교 기준으로 사용하기 좋습니다.)

  • dfs(root, 빈 배열 A)를 호출합니다.

  • ans를 반환합니다.

구현 예제

다음 파이썬 구현을 통해 더 잘 이해할 수 있습니다.

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 smallestFromLeaf(self, root):
        self.ans = "~"
        self.dfs(root,[])
        return self.ans
    def dfs(self, node, A):
        if node:
            A.append(chr(node.data + ord('a')))
            if not node.left and not node.right:
                self.ans = min(self.ans, ''.join(reversed(A)))
                A.pop()
                return
        self.dfs(node.left,A)
        self.dfs(node.right,A)
        A.pop()
        return
root = make_tree([25,1,3,1,3,0,2])
ob = Solution()
print(ob.smallestFromLeaf(root))

입력

[25,1,3,1,3,0,2]

출력

adz

정리

이 문제의 시간 복잡도는 트리의 모든 노드를 한 번씩 방문하므로 O(N)입니다. 단, 각 리프마다 경로 문자열을 생성하고 비교하는 비용이 추가되므로 실질적으로 O(N × H) 수준이라고 볼 수 있습니다(H는 트리의 높이). 공간 복잡도는 재귀 호출 스택과 경로 저장용 배열 때문에 O(H)입니다.

핵심 포인트는 두 가지입니다. 첫째, 문자열이 리프 → 루트 방향으로 만들어지므로 리프에서 도달했을 때 배열을 뒤집어야 한다는 점, 둘째, 초기 정답을 '~'처럼 충분히 큰 문자로 설정해 첫 번째 결과와의 비교가 항상 올바르게 동작하도록 하는 점입니다. 백트래킹으로 배열을 원상복구하는 것도 잊지 마세요.