오늘 문제는 이진 트리에 관한 문제입니다. 한 번 살펴볼까요?


1. 오늘의 학습 키워드

  • Tree
  • Binary Tree
  • Tree Traversal
  • DFS

2. 문제: 236. Lowest Common Ancestor of a Binary Tree

Description

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”

Example 1:

Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
Explanation: The LCA of nodes 5 and 1 is 3.

Example 2:

Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
Output: 5
Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.

Example 3:

Input: root = [1,2], p = 1, q = 2
Output: 1

Constraints:

  • The number of nodes in the tree is in the range [2, 105].
  • $-10^9$ <= Node.val <= $10^9$
  • All Node.val are unique.
  • p != q
  • p and q will exist in the tree.

3. 문제 풀이

Binary 트리 하나와 해당 트리에 속한 두 개의 노드가 주어집니다. 이 때, 두 노드의 공통 조상중 가장 낮은 node 즉, the lowest common ancestor (LCA)를 찾는 문제입니다.

Step1. 문제 이해하기

  • Input:
    • node개수는 2이상 10510^5입니다. 시간복잡도가 O(n2)O(n^2)보다 효율적이여야 합니다.
    • node값의 크기가 -10910^9 이상 10910^9이하 이므로 int형 값을 가진다는 것을 알 수 있습니다.
    • node값이 unique하기 때문에 예외 처리를 할 필요가 없습니다.
  • Output:
    • LCA node 값을 출력하면 됩니다.

트리 문제는 보통 트리 구현이나 트리 순회를 활용하는 문제 유형으로 나뉩니다. 따라서 O(n)O(n)의 시간복잡도가 나올것으로 예상됩니다.

Step2. 접근 방법

첫번째 예시를 살펴보겠습니다.

p = 5, q = 1입니다.

공통 조상은 3인것을 알 수 있습니다.

두 번째 예시를 살펴보겠습니다.

p = 5, q = 4입니다.

LCA는 5입니다.

또 다른 예시를 보겠습니다.

p = 6, q = 7, LCA = 5입니다.

이와 같이 여러 예시를 통해 공통 조상을 찾는 문제의 핵심 아이디어를 파악할 수 있습니다.

LCA를 찾기 위해 루트에서 자식 노드로 내려가며 탐색해야 합니다. 이 과정에서 자식 노드 중 하나라도 p 또는 q를 포함하면 해당 노드가 LCA 후보가 됩니다. 이를 통해 다음의 조건을 도출할 수 있습니다:

  1. 왼쪽, 오른쪽 자식 모두에서 pq 중 하나씩 발견되면 현재 노드가 공통 조상(LCA)입니다.
  2. 현재 노드가 pq 중 하나일 경우, 해당 노드를 그대로 반환합니다. 자식 노드에 p 또는 q가 없는 경우 LCA가 아닙니다.
  3. 한쪽 자식 노드에서만 p 또는 q를 찾은 경우에는 그 자식을 반환합니다. 이는 그 자식이 최종 LCA라는 뜻입니다.

Step3. 코드 설계

트리 순회를 통해 위의 조건을 효율적으로 확인할 수 있습니다. 후위 순회(DFS) 방식으로 트리를 탐색하며 조건을 검사하면 됩니다.

  1. 재귀 탐색을 이용해 자식 노드들을 먼저 확인합니다. 각 노드에서 왼쪽과 오른쪽 자식 노드로 재귀적으로 이동하여 pq를 찾습니다.
  2. 현재 노드가 p 또는 q일 경우 해당 노드를 반환합니다.
  3. 양쪽 자식 노드에서 모두 값을 반환받은 경우 현재 노드가 pq의 가장 낮은 공통 조상이 됩니다.
  4. 한쪽 자식 노드에서만 값을 받는다면 해당 노드가 LCA로 확정됩니다.

이 접근 방식으로 문제를 해결하면 시간 복잡도는 O(n)이 될 것으로 예상되며, 각 노드를 한 번씩만 방문하게 됩니다.

Step4. 코드 구현

# Definition for a binary tree node.
class TreeNode(object):
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        if root is None:
            return None
        left = self.lowestCommonAncestor(root.left,p,q)
        right = self.lowestCommonAncestor(root.right,p,q)
    
        if root.val == p.val or root.val == q.val:
            return root
        elif left and right:
            return root
        
        return left or right

코드 설명:

위의 코드는 재귀적으로 트리를 탐색하여 두 노드 pq의 가장 낮은 공통 조상(LCA)을 찾는 방식으로 작성되었습니다. 각 노드에서 다음과 같은 단계로 LCA를 탐색합니다:

  1. base 조건: 현재 노드가 None이면 None을 반환합니다.
  2. 현재 노드가 p 또는 q인 경우: 현재 노드가 p 또는 q와 일치하면 해당 노드를 반환합니다. 이는 현재 노드가 공통 조상의 후보가 될 수 있음을 의미합니다.
  3. 좌우 자식 노드에서 탐색: 왼쪽 자식과 오른쪽 자식 노드에서 각각 lowestCommonAncestor 재귀 호출을 수행합니다.
    • leftright 변수는 각각 왼쪽과 오른쪽에서 반환된 결과를 저장합니다.
  4. LCA 판별:
    • 만약 leftright가 모두 None이 아닌 경우, 즉, 두 자식 노드에서 각각 pq가 발견되었다면 현재 노드가 가장 낮은 공통 조상이므로, 현재 노드를 반환합니다.
    • 만약 한쪽에서만 값을 반환받았다면, 그쪽 노드가 pq의 공통 조상(LCA)이 될 수 있습니다. 따라서 left 또는 right 값을 반환합니다.
  5. LCA 반환: 루트부터 시작해 재귀적으로 탐색하여 최종적으로 LCA 노드를 반환합니다.

이 알고리즘은 깊이 우선 탐색(DFS)을 사용해 모든 노드를 한 번씩 방문하므로, 시간 복잡도는 O(n)입니다. 이는 트리의 모든 노드를 탐색하면서 pq를 찾기 때문입니다.

시간 복잡도: O(n)O(n)

결과:

https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/submissions/1450309282


4. 마무리

이번 문제에서는 이진 트리에서 두 노드의 가장 낮은 공통 조상을 찾는 방법을 다뤘습니다.

주요 포인트는 트리를 순회하며 각 노드의 자식 노드로 재귀 호출을 통해 pq가 존재하는 위치를 확인하는 것입니다. 이때, 현재 노드에서 왼쪽과 오른쪽 자식 노드로부터 각각 pq가 발견될 경우 그 노드가 바로 LCA임을 확정할 수 있습니다.

이 문제를 통해 다음과 같은 트리 탐색과 관련된 중요한 개념들을 복습할 수 있었습니다:

  • 재귀적 탐색을 통해 문제를 분할하여 해결하는 방식
  • LCA 판별을 위해 양쪽 자식 노드의 반환 값을 활용하는 기법
  • 깊이 우선 탐색(DFS)을 활용한 효율적인 탐색 방법

이러한 논리 구조는 다양한 트리 기반 문제에 적용할 수 있으며, 특히 트리의 특정 노드 간 관계를 파악해야 할 때 유용합니다.

읽어주셔서 감사합니다.

전체 코드는 다음 링크에서 확인할 수 있습니다. 이 코드에는 해당 문제를 3가지 예시를 돌리는 코드까지 나와있습니다.

오늘도 화이팅!!

profile
할 수 있다

0개의 댓글