자바 코딩테스트 강좌, 최소 신장 트리 구하기

안녕하세요! 오늘은 알고리즘 시험에서 자주 출제되는 최소 신장 트리(Minimum Spanning Tree) 문제에 대해 알아보겠습니다. 특히, 자바로 이 문제를 해결하는 방법을 심층적으로 다뤄볼 것입니다.

문제 설명

다음과 같은 가중치가 있는 연결 그래프가 있다고 가정해봅시다. 이 그래프의 모든 정점이 연결되어 있으며, 가중치는 서로 다릅니다. 여러분의 목표는 이 그래프의 최소 신장 트리를 구하는 것입니다.

입력 형식

  • 첫 번째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다.
  • 다음 E개의 줄에는 각 간선의 두 정점과 가중치 u, v, w가 주어진다.

출력 형식

최소 신장 트리를 구성하는 간선의 목록과 그 가중치의 합을 출력하시오.

예제 입출력

입력 예시

        3 3
        1 2 1
        2 3 2
        1 3 3
        

출력 예시

        1 - 2
        2 - 3
        총 가중치: 3
        

문제 풀이 접근 방식

최소 신장 트리를 찾기 위해 사용되는 대표적인 알고리즘에는 크루스칼 알고리즘(Kruskal’s Algorithm)프림 알고리즘(Prim’s Algorithm)이 있습니다. 여기서는 크루스칼 알고리즘을 이용하여 이 문제를 풀어보겠습니다.

크루스칼 알고리즘 개요

  1. 간선을 가중치에 따라 오름차순으로 정렬한다.
  2. 가장 작은 가중치의 간선을 선택하고, 사이클이 생기지 않는다면 이 간선을 결과에 포함시킨다.
  3. 위 과정을 반복하여 모든 정점이 연결될 때까지 진행한다.

자바 코드 구현

이제 실제로 자바 코드를 통해 크루스칼 알고리즘을 구현해보겠습니다.


import java.util.Arrays;

public class Kruskal {
    static class Edge implements Comparable {
        int u, v, weight;

        Edge(int u, int v, int weight) {
            this.u = u;
            this.v = v;
            this.weight = weight;
        }

        @Override
        public int compareTo(Edge other) {
            return this.weight - other.weight;
        }
    }

    static int[] parent;
    static int[] rank;

    public static void main(String[] args) {
        int V = 3; // 정점의 개수
        int E = 3; // 간선의 개수
        Edge[] edges = new Edge[E];

        edges[0] = new Edge(1, 2, 1);
        edges[1] = new Edge(2, 3, 2);
        edges[2] = new Edge(1, 3, 3);

        Arrays.sort(edges);

        parent = new int[V + 1];
        rank = new int[V + 1];

        for (int i = 1; i <= V; i++) {
            parent[i] = i;
            rank[i] = 0;
        }

        int totalWeight = 0;
        System.out.println("최소 신장 트리의 간선:");
        for (Edge edge : edges) {
            if (union(edge.u, edge.v)) {
                System.out.println(edge.u + " - " + edge.v);
                totalWeight += edge.weight;
            }
        }
        System.out.println("총 가중치: " + totalWeight);
    }

    static int find(int u) {
        if (parent[u] != u) {
            parent[u] = find(parent[u]);
        }
        return parent[u];
    }

    static boolean union(int u, int v) {
        int rootU = find(u);
        int rootV = find(v);
        if (rootU != rootV) {
            if (rank[rootU] < rank[rootV]) {
                parent[rootU] = rootV;
            } else if (rank[rootU] > rank[rootV]) {
                parent[rootV] = rootU;
            } else {
                parent[rootV] = rootU;
                rank[rootU]++;
            }
            return true;
        }
        return false;
    }
}
    

코드 설명

위 코드에서는 다음의 주요 기능들이 구현되어 있습니다:

  1. Edge 클래스: 간선을 나타내는 클래스로, 가중치 기준으로 비교 가능하도록 설정되어 있습니다.
  2. find 메서드: 유니온 파인드(Union-Find) 알고리즘을 통해 루트 노드를 찾아 반환합니다. 경로 압축(Path Compression)을 통해 성능을 향상시킵니다.
  3. union 메서드: 두 정점의 루트 노드가 다르면 합치고, 성공적으로 합쳐졌는지 여부를 반환합니다. 이를 통해 사이클을 체크합니다.

테스트 및 실행

이 코드를 컴파일하고 실행시키면, 다음과 같은 결과가 출력됩니다:

최소 신장 트리의 간선:
1 - 2
2 - 3
총 가중치: 3
    

최소 신장 트리에 대한 응용

최소 신장 트리는 다양한 분야에서 활용될 수 있습니다. 예를 들어:

  • 네트워크 설계: 컴퓨터 네트워크를 설계할 때 최소 비용으로 연결 가능한 경로를 결정할 때 유용합니다.
  • 도로망 구축: 도시의 도로망을 최소 비용으로 구축할 때 사용될 수 있습니다.
  • 전력망 설계: 전력망의 비용 절감을 위해 신장 트리 알고리즘이 활용됩니다.

결론

오늘은 최소 신장 트리 문제를 크루스칼 알고리즘을 통해 해결하는 방법에 대해 알아보았습니다. 이 알고리즘은 시간 복잡도가 O(E log E)로, 간선의 개수에 비례하여 효율적입니다. 알고리즘 문제 해결을 위한 준비 과정에서 이와 같은 문제를 여러 번 연습해보시길 권장합니다. 앞으로 여러분의 코딩 테스트 준비에 도움이 되길 바랍니다!

자바 코딩테스트 강좌, 최소 비용 구하기

코딩테스트 준비를 위해선 다양한 알고리즘 문제를 풀어보는 것이 중요합니다. 이번 강좌에서는 ‘최소 비용 구하기’ 문제를 다뤄보겠습니다. 이 문제는 그래프 이론과 동적 프로그래밍(Dynamic Programming) 기술을 필요로 합니다. 여러분의 프로그래밍 능력을 한층 더 강화해 줄 것입니다.

문제 설명

주어진 방향 그래프에서 여러 개의 노드와 간선이 있습니다. 각 간선은 특정 비용을 가지고 있습니다. 우리는 한 노드에서 시작하여 다른 노드로 이동할 때 드는 최소 비용을 구해야 합니다. 노드는 여러 개가 있을 수 있으며, 어떤 두 노드 사이에는 여러 개의 경로가 존재할 수 있습니다.

입력

  • 첫 번째 줄엔 노드의 개수 N과 간선의 개수 M이 주어진다 (1 ≤ N ≤ 100, 1 ≤ M ≤ 1000)
  • 다음 M개의 줄엔 간선 정보가 주어진다. 각 간선은 u v c의 형식으로 주어지며, 이는 노드 u에서 v로 가는 간선의 비용이 c임을 뜻한다. (1 ≤ c ≤ 1000)
  • 마지막 줄엔 시작 노드 S와 목표 노드 T가 주어진다.

출력

시작 노드 S에서 목표 노드 T로 가는 최소 비용을 출력하라. 만약 도달할 수 없다면 -1을 출력한다.

문제 예시

    
    입력:
    5 6
    1 2 2
    1 3 3
    2 3 1
    2 4 4
    3 4 1
    4 5 1
    1 5
    
    출력:
    6
    
    

알고리즘 아이디어

이 문제는 그래프에서 최단 경로를 찾는 문제와 유사합니다. 다익스트라(Dijkstra) 알고리즘을 사용하여 효율적으로 문제를 해결할 수 있습니다. 이 알고리즘은 한 노드에서 다른 노드로의 최단 경로를 찾는데 최적화되어 있습니다. 우리는 이 알고리즘을 사용하여 시작 노드에서 목표 노드까지의 최소 비용을 추적합니다.

다익스트라 알고리즘 설명

다익스트라 알고리즘은 다음과 같은 방식으로 작동합니다:

  1. 우선, 시작 노드에서 각 노드까지의 거리를 무한대로 초기화하되 시작 노드의 거리는 0으로 초기화합니다.
  2. 우선순위 큐를 사용하여 현재 최소 비용으로 접근할 수 있는 노드를 선택합니다.
  3. 선택한 노드의 인접한 노드에 대해 비용을 갱신합니다. 만약 새로운 경로가 더 나은 비용이라면 해당 노드를 업데이트하고 큐에 추가합니다.
  4. 목표 노드에 도달할 때까지 이 과정을 반복합니다. 모든 노드를 처리한 이후 목표 노드의 최종 거리를 반환합니다.

자바 코드 구현

    
    import java.util.*;

    public class MinCostPath {
        static class Edge {
            int to, cost;

            Edge(int to, int cost) {
                this.to = to;
                this.cost = cost;
            }
        }

        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
        
            int N = sc.nextInt(); // 노드 수
            int M = sc.nextInt(); // 간선 수
            
            List> graph = new ArrayList<>();
            for (int i = 0; i <= N; i++) {
                graph.add(new ArrayList<>());
            }
        
            for (int i = 0; i < M; i++) {
                int u = sc.nextInt();
                int v = sc.nextInt();
                int c = sc.nextInt();
                graph.get(u).add(new Edge(v, c));
            }
            
            int S = sc.nextInt(); // 시작 노드
            int T = sc.nextInt(); // 목표 노드
            
            System.out.println(dijkstra(N, graph, S, T));
        }
        
        static int dijkstra(int n, List> graph, int start, int target) {
            int[] dist = new int[n + 1];
            Arrays.fill(dist, Integer.MAX_VALUE);
            dist[start] = 0;
        
            PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(edge -> edge.cost));
            pq.add(new Edge(start, 0));
        
            while (!pq.isEmpty()) {
                Edge current = pq.poll();
        
                if (current.to == target) {
                    return dist[target];
                }
        
                for (Edge edge : graph.get(current.to)) {
                    if (dist[current.to] + edge.cost < dist[edge.to]) {
                        dist[edge.to] = dist[current.to] + edge.cost;
                        pq.add(new Edge(edge.to, dist[edge.to]));
                    }
                }
            }
        
            return -1; // 도달할 수 없는 경우
        }
    }
    
    

코드 설명

위 코드에서:

  • 우선 노드 수(N)와 간선 수(M)를 입력받습니다.
  • 각 간선에 대한 정보를 입력받고 그래프를 인접 리스트 형태로 저장합니다.
  • 다익스트라 알고리즘을 위한 초기화 작업을 진행합니다. 거리 배열(dist)을 생성하고 시작 노드의 거리를 0으로 설정합니다.
  • 우선순위 큐를 사용하여 현재 노드의 인접 노드에 대한 최단 경로를 갱신하며 반복합니다.
  • 목표 노드에 도달하면 그 거리를 반환하고, 도달할 수 없는 경우 -1을 반환합니다.

최적화와 주의사항

다익스트라 알고리즘은 모든 간선의 가중치가 0 이상일 때 유효합니다. 만약 음수 간선이 있을 경우 벨만 포드 알고리즘을 고려해야 합니다. 또한, 성능 최적화를 위해 우선순위 큐를 사용할 때는 최소 힙을 사용하는 것이 좋습니다.

결론

이번 강좌에서는 ‘최소 비용 구하기’ 문제를 통해 다익스트라 알고리즘을 학습했습니다. 알고리즘 문제를 해결하는 연습을 통해 코딩테스트에 대한 자신감을 얻고, 문제 해결 능력을 더욱 향상시킬 수 있기를 바랍니다. 다음 시간에는 더 다양한 알고리즘을 소개하겠습니다.

자바 코딩테스트 강좌, 최소 신장 트리

1. 최소 신장 트리란?

최소 신장 트리(Minimum Spanning Tree, MST)는 가중치가 할당된 무방향 그래프에서 모든 정점을 포함하면서 전체 가중치의 합이 최소가 되는 트리를 말합니다. 최소 신장 트리는 네트워크 설계, 클러스터링 및 여러 최적화 문제에 사용됩니다.

2. 알고리즘 개요

최소 신장 트리를 찾기 위한 대표적인 알고리즘으로는 크루스칼 알고리즘(Kruskal’s Algorithm)과 프림 알고리즘(Prim’s Algorithm)이 있습니다. 이 두 가지 알고리즘은 각각 다른 방식으로 MST를 구성합니다.

2.1. 크루스칼 알고리즘

크루스칼 알고리즘은 그래프의 간선을 가중치에 따라 오름차순으로 정렬한 후, 사이클을 형성하지 않도록 간선을 추가하여 MST를 구성하는 방식입니다.

2.2. 프림 알고리즘

프림 알고리즘은 시작 정점에서 출발하여 현재 연결된 정점 중 가장 가중치가 낮은 간선을 선택하여 MST를 확장해 나가는 방식입니다.

2.3. 알고리즘 선택

두 가지 알고리즘 모두 효율적으로 MST를 찾을 수 있지만, 크루스칼은 간선의 수가 적은 희소 그래프에 더 적합하고, 프림은 정점의 수가 적은 밀접 그래프에 적합합니다.

3. 문제 설명

문제: 최소 신장 트리 만들기

가중치가 있는 무방향 그래프가 주어질 때, 최소 신장 트리를 구하시오. 입력은 다음과 같은 형식입니다:

  • 첫 번째 줄에 정점의 수 V와 간선의 수 E가 주어진다.
  • 다음 E개의 줄에 각각 u, v, w가 주어져, 이는 정점 uv를 연결하는 가중치 w의 간선을 의미한다.

출력으로 최소 신장 트리의 가중치 합을 출력하시오.

4. 문제 풀이 접근법

문제를 해결하기 위해 다음 단계를 따릅니다:

  1. 입력값을 읽어들인다.
  2. 간선을 가중치에 따라 정렬한다.
  3. 유니온-파인드(Union-Find) 자료구조를 사용하여 사이클을 검출한다.
  4. 사이클이 형성되지 않는 간선을 MST에 추가한다.
  5. MST의 가중치 합을 계산하여 출력한다.

5. 자바 코드 구현

5.1. 유니온-파인드 자료구조 구현

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
            rank[i] = 0;
        }
    }

    public int find(int p) {
        if (parent[p] != p) {
            parent[p] = find(parent[p]);
        }
        return parent[p];
    }

    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP != rootQ) {
            if (rank[rootP] > rank[rootQ]) {
                parent[rootQ] = rootP;
            } else if (rank[rootP] < rank[rootQ]) {
                parent[rootP] = rootQ;
            } else {
                parent[rootQ] = rootP;
                rank[rootP]++;
            }
        }
    }
}
    

5.2. 크루스칼 알고리즘 구현

import java.util.*;

class Edge implements Comparable {
    int u, v, weight;

    public Edge(int u, int v, int weight) {
        this.u = u;
        this.v = v;
        this.weight = weight;
    }

    @Override
    public int compareTo(Edge other) {
        return Integer.compare(this.weight, other.weight);
    }
}

public class MinimumSpanningTree {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int V = scanner.nextInt();
        int E = scanner.nextInt();

        PriorityQueue edgeList = new PriorityQueue<>();
        for (int i = 0; i < E; i++) {
            int u = scanner.nextInt();
            int v = scanner.nextInt();
            int weight = scanner.nextInt();
            edgeList.add(new Edge(u, v, weight));
        }

        UnionFind uf = new UnionFind(V);
        int totalWeight = 0;

        while (!edgeList.isEmpty()) {
            Edge edge = edgeList.poll();
            if (uf.find(edge.u) != uf.find(edge.v)) {
                uf.union(edge.u, edge.v);
                totalWeight += edge.weight;
            }
        }

        System.out.println(totalWeight);
    }
}
    

6. 코드 설명

위의 코드는 크루스칼 알고리즘을 구현한 것입니다. 먼저, 입력으로 정점의 수와 간선의 수를 읽고 각 간선에 대한 정보를 저장합니다. 이후 간선을 가중치에 따라 정렬하고, 유니온-파인드 자료구조를 사용하여 사이클을 형성하지 않는 간선을 선택합니다. 최종적으로, 선택된 간선의 가중치 합을 출력합니다.

7. 시간 복잡도

크루스칼 알고리즘의 시간 복잡도는 일반적으로 O(E log E)로, 이는 간선 정렬에 대한 복잡도입니다. 유니온-파인드 연산은 매우 효율적이며 거의 상수 시간에 수행됩니다.

8. 마무리

최소 신장 트리는 그래프 이론에서 중요한 개념으로, 다양한 최적화 문제에 활용됩니다. 크루스칼과 프림 알고리즘을 이해하고 구현함으로써 최소 신장 트리를 쉽게 구할 수 있습니다. 이번 강좌를 통해 MST의 개념을 숙지하고, 실제 코딩테스트 문제에 적용할 수 있는 능력을 키우길 바랍니다.

자바 코딩테스트 강좌, 최소 공통 조상 구하기 2

문제 설명

주어진 이진 트리에서 두 노드의 최소 공통 조상(LCA)을 찾는 문제는 다양한 알고리즘 문제 해결에서 중요하게 다뤄집니다. 이 문제는 특히 트리 구조를 이해하고 재귀적인 사고를 요구합니다. 이 글에서는 최소 공통 조상을 구하는 방법을 심층적으로 살펴보겠습니다.

문제 정의

이진 트리의 루트 노드와 두 노드 A, B가 주어질 때, A와 B의 최소 공통 조상을 찾는 문제를 해결해야 합니다. 최소 공통 조상은 다음 조건을 만족하는 가장 깊은 (최하위) 노드로 정의됩니다:

  • 노드 A와 B의 모든 조상이 포함되어야 합니다.
  • 노드 LCA는 A와 B가 위치한 서브트리 안에 존재해야 합니다.

제약 조건

주어진 이진 트리는 다음과 같은 제약을 가집니다:

  • 노드의 수는 1 이상 10^4 이하입니다.
  • 각 노드는 서로 다른 값을 가지고 있습니다.
  • A와 B는 반드시 트리에 존재하는 노드입니다.

문제 해결 전략

1. 트리 구조 이해하기

트리는 계층적 데이터 구조로 노드와 간선으로 구성됩니다. 이진 트리의 경우, 각 노드는 최대 두 개의 자식을 가질 수 있습니다. 트리의 깊이를 기반으로 최소 공통 조상을 찾기 위해 다음과 같은 자원을 확보해야 합니다.

2. 알고리즘 선택

후보 알고리즘으로는 DFS(Depth-First Search)를 사용할 수 있습니다. 이 접근 방식은 다음과 같은 단계로 진행됩니다:

  1. 루트 노드에서 시작하여 좌우 서브트리를 탐색합니다.
  2. 두 노드 A 및 B가 각각의 서브트리에 존재하는 경우, 현재 노드가 LCA가 됩니다.
  3. A 또는 B가 하나의 서브트리에만 존재하는 경우, 해당 노드를 반환합니다.
  4. 둘 다 존재하지 않으면 null을 반환합니다.

파이썬 구현


# 이진 트리 노드 정의
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# LCA 찾기
def lowestCommonAncestor(root, p, q):
    if root is None:
        return None
    
    if root == p or root == q:
        return root
    
    left = lowestCommonAncestor(root.left, p, q)
    right = lowestCommonAncestor(root.right, p, q)
    
    if left and right:
        return root
    return left if left else right
    

구현 설명

1. TreeNode 클래스

먼저, TreeNode 클래스를 정의하여 각 노드의 값을 저장합니다. 노드는 값 (val) 외에도 왼쪽 자식 (left)과 오른쪽 자식 (right) 노드를 각각 참조합니다.

2. lowestCommonAncestor 함수

주어진 루트 노드에서 시작하여 재귀적으로 호출하여 두 노드 A와 B의 LCA를 찾아냅니다. 기저 사례로는 노드가 null이거나 루트가 A 또는 B일 경우, 현재 노드를 반환합니다.

각각의 서브트리에서 LCA를 찾는 중이기 때문에, 왼쪽 서브트리와 오른쪽 서브트리에서 반환된 값이 모두 존재하면, 현재 노드가 LCA가 됩니다.

3. 문제 해결

이제 이 알고리즘을 사용하여 주어진 이진 트리의 두 노드 A, B에 대한 LCA를 찾을 수 있습니다. 알고리즘의 시간 복잡도는 O(N)이며, N은 노드의 수입니다.

성능 검증

이 알고리즘을 다양한 입력에 대해 테스트하여 성능을 검증할 수 있습니다. 예를 들어, 입력으로 다음과 같은 이진 트리를 고려해 볼 수 있습니다.


    # 예시 트리 생성
    root = TreeNode(3)
    root.left = TreeNode(5)
    root.right = TreeNode(1)
    root.left.left = TreeNode(6)
    root.left.right = TreeNode(2)
    root.right.left = TreeNode(0)
    root.right.right = TreeNode(8)
    root.left.right.left = TreeNode(7)
    root.left.right.right = TreeNode(4)

    p = root.left         # Node with value 5
    q = root.left.right.right  # Node with value 4

    ancestor = lowestCommonAncestor(root, p, q)
    print(ancestor.val)  # Expected output: 5
    

결론

이번 강좌에서는 자바 코딩테스트의 중요한 개념 중 하나인 최소 공통 조상 구하기에 대해 심도 있게 다루었습니다. 이 알고리즘은 다양한 상황에서 활용될 수 있으며, 트리 구조를 이해하는 데 매우 유용합니다. 문제 해결 과정과 알고리즘 분석을 통해 코딩 테스트 대비에 도움이 되길 바랍니다.

추가적인 질문이 있다면 댓글로 남겨 주세요. 감사합니다!

자바 코딩테스트 강좌, 최소 공통 조상 구하기 1

코딩 테스트 준비생들을 위한 알고리즘 문제 풀이 강좌입니다. 이번 글에서는 최소 공통 조상을 구하는 알고리즘을 다루고, 이를 해결하기 위한 다양한 전략과 자바 코드를 설명하겠습니다.

문제 정의

주어진 이진 트리에서 두 노드의 최소 공통 조상(LCA: Lowest Common Ancestor)을 찾는 문제입니다. LCA는 두 노드의 조상 중에서 가장 낮은 노드를 의미합니다. 이 문제는 트리 구조와 재귀적인 탐색 방법을 이해하는 데 중요한 예제입니다.

예를 들어, 다음과 같은 이진 트리가 주어진다면:

                 3
                / \
               5   1
              / \ / \
             6  2 0  8
               / \
              7   4
            

노드 5와 1의 최소 공통 조상은 3이고, 노드 5와 4의 최소 공통 조상은 5입니다.

문제 접근법

이 문제를 해결하기 위해서는 다양한 접근법이 있을 수 있지만, 가장 일반적인 방법은 깊이 우선 탐색(DFS: Depth First Search)을 사용하는 것입니다. 아래의 두 가지 접근 방법을 통해 문제를 해결하는 방식을 살펴보겠습니다.

1. 재귀적 DFS 접근법

재귀를 사용하여 이진 트리를 탐색하면서 각 노드가 두 노드의 조상인지 확인하는 방법입니다. 기본적인 아이디어는 다음과 같습니다.

  • 현재 노드가 null인 경우 null을 반환합니다.
  • 현재 노드가 처음 찾으려는 노드 중 하나인 경우 현재 노드를 반환합니다.
  • 좌측 및 우측 자식 노드를 재귀적으로 호출하여 결과를 받습니다.
  • 좌측 및 우측 자식 노드에서 반환된 값이 모두 null이 아닌 경우, 현재 노드가 최소 공통 조상입니다.
  • 그 외의 경우에는 null이 아닌 자식을 반환합니다.

2. 부모 노드 맵을 이용한 탐색

이 방법에서는 각 노드의 부모를 저장하는 맵을 먼저 만든 후, 다음과 같은 과정을 진행합니다.

  • 첫 번째 노드부터 시작하여 부모 노드를 맵에 저장합니다.
  • 두 번째 노드의 조상을 저장한 후, 각 조상을 부모 맵에서 확인합니다.
  • 첫 번째 노드에서 두 번째 노드의 조상으로 올라가면서 최초로 만나는 조상이 최소 공통 조상입니다.

자바 코드 구현

이제 위에서 설명한 두 접근법을 사용하여 자바 코드로 구현해보겠습니다.

첫 번째 접근법: 재귀적 DFS

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        
        if (left != null && right != null) {
            return root;
        }
        return left != null ? left : right;
    }
}
            

두 번째 접근법: 부모 노드 맵 이용

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

public class Solution {
    private Map parentMap = new HashMap<>();
    
    public void buildParentMap(TreeNode root) {
        if (root.left != null) {
            parentMap.put(root.left, root);
            buildParentMap(root.left);
        }
        if (root.right != null) {
            parentMap.put(root.right, root);
            buildParentMap(root.right);
        }
    }
    
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        buildParentMap(root);
        
        HashSet ancestors = new HashSet<>();
        
        while (p != null) {
            ancestors.add(p);
            p = parentMap.get(p);
        }
        
        while (!ancestors.contains(q)) {
            q = parentMap.get(q);
        }
        return q;
    }
}
            

테스트 케이스

이 구현을 검증하기 위해 몇 가지 테스트 케이스를 살펴보겠습니다:

  • 트리: 위의 예시와 같이 구성하고, 노드 5와 1의 LCA를 찾습니다. 결과는 3이어야 합니다.
  • 트리: 또한, 노드 5와 4의 LCA를 찾습니다. 결과는 5이어야 합니다.
  • 트리: 노드 6과 7의 경우 LCA는 5이어야 합니다.

결론

이번 글에서는 자바를 사용하여 이진 트리의 최소 공통 조상을 찾는 방법에 대해 자세히 알아보았습니다. 재귀적 접근법과 부모 노드 맵을 이용한 접근법을 통해 LCA를 효과적으로 찾을 수 있습니다. 이러한 알고리즘을 이해하고 구현하는 것은 프로그래밍 인터뷰에서 좋은 평가를 받을 수 있는 기초가 됩니다.

문제 해결 능력을 기르는 것은 중요하며, 다양한 문제를 풀고 경험을 쌓는 것이 필요합니다. 충분한 연습을 통해 코딩 테스트에 대비하세요!