C# 코딩테스트 강좌, 외판원의 순회 경로 짜기

코딩테스트에서 자주 출제되는 문제 중 하나가 바로 ‘외판원의 순회 문제’입니다. 이 문제는 주어진 여러 도시들 사이를 여행하며 돌아오는 경로를 최소 비용으로 찾는 문제입니다. 이 문제는 NP-완전 문제에 속하여, 최적해를 찾는 것이 매우 어렵습니다. 따라서, 다양한 탐색 알고리즘을 통해 근사적인 해를 찾거나, 동적 프로그래밍(DP)을 이용하여 최적해를 찾는 방식이 일반적입니다.

문제 정의

n개의 도시가 있으며, 각 도시는 서로 다른 다른 도시와 연결되어 있다고 가정합니다. 각 도시 간의 이동 비용이 주어질 때, 모든 도시를 한 번씩 방문하고 출발 도시로 되돌아오는 최소 비용의 순회를 출력하시오.

입력

  • 첫 번째 줄에는 도시의 수 n (1 ≤ n ≤ 10)이 주어진다.
  • 다음 n개의 줄에는 n x n 크기의 인접 행렬이 주어진다. 각 행렬의 원소는 두 도시 간의 이동 비용을 나타낸다. 이동 비용이 없는 경우에는 0이 주어진다.

출력

모든 도시를 한 번씩 방문하고 다시 출발지로 돌아오는 최소 비용을 출력한다.

예제

        입력:
        4
        0 10 15 20
        10 0 35 25
        15 35 0 30
        20 25 30 0

        출력:
        80
    

문제 해결 과정

1. 문제 분석

문헌에 따르면 이 문제는 NP-완전 문제로 알려져 있어, 모든 가능한 경로를 시도하는 완전 탐색 방식으로 접근할 수 있습니다. 그러나 도시의 수가 10개 이하일 때는 이러한 방식이 실효성이 있다는 것을 보여줄 수 있습니다. 각 도시를 한 번씩 방문하는 경우의 수는 (n-1)! 이기 때문에, n이 10일 경우 9! = 362880 이라는 충분히 계산 가능한 범위입니다.

2. 알고리즘 선택

여기서는 백트래킹 기법을 통해 모든 경로를 탐색하여 최소 비용을 계산하는 알고리즘을 선택하겠습니다. 이 알고리즘은 현재 도시를 기준으로 가능한 경로를 재귀적으로 탐색하며, 더 이상 진행할 수 없는 경우에는 이전 단계로 돌아가서 다른 경로를 시도합니다. 이를 통해 모든 경우의 수를 고려하여 최소 비용을 찾을 수 있습니다.

3. C# 코드 구현

        
        using System;

        class Program
        {
            static int n; // 도시의 수
            static int[,] cost; // 이동 비용 행렬
            static bool[] visited; // 방문 도시 체크
            static int minCost = int.MaxValue; // 최소 비용

            static void Main(string[] args)
            {
                n = int.Parse(Console.ReadLine());
                cost = new int[n, n];
                visited = new bool[n];

                for (int i = 0; i < n; i++)
                {
                    var line = Console.ReadLine().Split();
                    for (int j = 0; j < n; j++)
                    {
                        cost[i, j] = int.Parse(line[j]);
                    }
                }

                visited[0] = true; // 시작 도시 방문 표시
                FindPath(0, 0, 1); // 현재 도시(0), 현재 비용(0), 방문 도시 수(1)
                Console.WriteLine(minCost);
            }

            static void FindPath(int currentCity, int currentCost, int count)
            {
                if (count == n && cost[currentCity, 0] != 0) // 모든 도시를 방문했다면
                {
                    minCost = Math.Min(minCost, currentCost + cost[currentCity, 0]);
                    return;
                }

                for (int nextCity = 0; nextCity < n; nextCity++)
                {
                    if (!visited[nextCity] && cost[currentCity, nextCity] != 0)
                    {
                        visited[nextCity] = true;
                        FindPath(nextCity, currentCost + cost[currentCity, nextCity], count + 1);
                        visited[nextCity] = false; // 백트래킹
                    }
                }
            }
        }
        
        

4. 코드 설명

위의 코드는 도시 수를 입력받아 주어진 인접 행렬을 생성합니다. 그런 다음, FindPath 함수를 사용하여 모든 경로를 탐색합니다. 이 함수의 주요 매개변수는 다음과 같습니다:

  • currentCity: 현재 방문 중인 도시
  • currentCost: 지금까지의 이동 비용
  • count: 현재까지 방문한 도시 수

기본적으로 시작 도시는 방문 표시가 되어 있으며, 모든 도시를 방문하게 되면 처음 도시로의 경비를 계산하고 최소 비용과 비교합니다. 만약, 더 줄일 수 있다면 minCost를 갱신합니다.

5. 시간 복잡도

이 문제의 시간 복잡도는 O(n!)입니다. 모든 도시를 방문하는 조합을 탐색하므로 도시 수가 증가할수록 계산량이 기하급수적으로 증가하게 됩니다.

결론

이번 강좌를 통해 외판원의 순회의 최소 비용을 찾는 문제를 C#로 해결하는 방법을 다루었습니다. 해당 문제는 기본적인 백트래킹 알고리즘을 통해 해결할 수 있으며, 더 좋은 효율성을 위해 다양한 최적화 기법과 동적 프로그래밍을 활용할 수도 있습니다. 향후에는 이러한 기법들에 대해서도 다루어보도록 하겠습니다.

C# 코딩테스트 강좌, 케빈 베이컨의 6단계 법칙

이번 글에서는 매우 흥미로운 개념인 “케빈 베이컨의 6단계 법칙”에 대해 알아보겠습니다. 이 법칙은 모든 사람이 6단계 이내에 케빈 베이컨과 연결될 수 있다는 이론입니다. 영화 산업에서 자주 인용되며, 사회적 네트워킹 이론에서도 중요한 역할을 합니다. 우리는 이 개념을 바탕으로 알고리즘 문제를 풀어보겠습니다.

문제 설명

다음과 같은 조건의 알고리즘 문제를 다룹니다:

문제 제목: 케빈 베이컨의 친구 찾기

문제 설명: N명의 배우가 있고, 각 배우는 다른 배우와 친구일 수 있습니다. 주어진 N명의 배우와 그들의 친구 관계가 있을 때, 각 배우와 케빈 베이컨 간의 관계의 최소 단계(최소 친구의 수)가 몇 단계인지 계산하는 문제입니다. 이러한 관계는 비행기로 비유할 수 있으며, 관계의 간선은 서로가 친구임을 의미합니다.

한 배우 A와 다른 배우 B의 친구 관계가 주어진다면, 두 배우는 1단계 관계에 있습니다. A와 B의 친구 C가 있다면, A와 C는 2단계 관계에 있습니다. 이와 같이 진행하여, 모든 배우들과 케빈 베이컨 간의 최소 친구 관계를 계산해야 합니다.

입력 형식

  • 첫 번째 줄에 자연수 N (1 ≤ N ≤ 100)과 M (0 ≤ M ≤ 10,000)이 주어집니다.
  • 두 번째 줄 부터 M줄에 걸쳐서 두 배우의 친구 관계가 주어집니다. 예를 들어 “1 2″는 1번 배우와 2번 배우가 서로 친구임을 의미합니다.

출력 형식

각 배우에 대해 케빈 베이컨과의 최소 관계의 단계를 출력합니다. 만약 케빈 베이컨과 관계가 없다면 -1을 출력해야 합니다.

입력 예시

5 4
1 2
1 3
2 4
3 5

출력 예시

2
3
3

문제 풀이 접근 방식

이 문제를 해결하기 위해서는 그래프 탐색 알고리즘을 사용할 수 있습니다. 친구 관계를 그래프로 구성하고, 각 배우에서 케빈 베이컨까지의 최단 경로를 찾는 방식입니다. 이를 위해 다음과 같은 단계를 따릅니다:

  1. 그래프 구성: 주어진 친구 관계를 인접 리스트 방식으로 그래프를 구성합니다.
  2. BFS (너비 우선 탐색): 모든 배우에 대해 BFS를 수행하여 케빈 베이컨까지의 최단 경로를 찾습니다.
  3. 출력: 각 배우와 케빈 베이컨의 최소 관계 단계를 계산하여 출력합니다.

코드 구현

이제 위의 접근 방식을 C#으로 구현해보겠습니다.


using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        int N, M;
        string[] firstLine = Console.ReadLine().Split();
        N = int.Parse(firstLine[0]);
        M = int.Parse(firstLine[1]);

        List[] graph = new List[N + 1];
        for (int i = 1; i <= N; i++)
        {
            graph[i] = new List();
        }

        for (int i = 0; i < M; i++)
        {
            string[] edge = Console.ReadLine().Split();
            int u = int.Parse(edge[0]);
            int v = int.Parse(edge[1]);
            graph[u].Add(v);
            graph[v].Add(u); // 친구 관계는 양방향입니다.
        }

        int[] distances = new int[N + 1];

        for (int i = 1; i <= N; i++)
        {
            BFS(graph, distances, i);
        }

        for (int i = 1; i < distances.Length; i++)
        {
            Console.WriteLine(distances[i] == 0 ? -1 : distances[i]);
        }
    }

    static void BFS(List[] graph, int[] distances, int start)
    {
        Queue queue = new Queue();
        bool[] visited = new bool[graph.Length];
        int[] localDistances = new int[graph.Length];
        
        queue.Enqueue(start);
        visited[start] = true;

        while (queue.Count > 0)
        {
            int node = queue.Dequeue();

            foreach (var neighbor in graph[node])
            {
                if (!visited[neighbor])
                {
                    visited[neighbor] = true;
                    queue.Enqueue(neighbor);
                    localDistances[neighbor] = localDistances[node] + 1;
                }
            }
        }

        for (int j = 1; j < distances.Length; j++)
        {
            if (distances[j] == 0 && localDistances[j] > 0)
            {
                distances[j] = localDistances[j];
            }
        }
    }
}

해설

이 코드에서는 먼저 배우의 수와 친구 관계의 수를 입력받고, 친구 관계를 기반으로 그래프를 구성합니다. 이후 각 배우에 대해 BFS를 수행하여 케빈 베이컨까지의 거리를 계산합니다. BFS는 큐를 활용하여 현재 노드와 연결된 모든 노드를 탐색하고, 최단 거리 계산에 매우 적합한 알고리즘입니다.

각 배우 별로 BFS 결과를 통해 최소 친구 관계 단계를 출력합니다. 만약 케빈 베이컨과 연결되지 않았다면, -1을 출력하도록 설정했습니다.

최적화 및 추가 사항

정확한 결과를 얻기 위해 몇 가지 최적화를 고려할 수 있습니다:

  • 친구 관계가 없는 경우를 고려하여 초기 거리 배열을 설정할 때, 모든 요소를 -1로 초기화할 수 있습니다.
  • BFS를 통해 탐색할 때, 중복 노드를 제거하여 메모리를 절약할 수 있습니다.

결론

이번 강좌에서는 케빈 베이컨의 6단계 법칙을 기반으로 알고리즘 문제를 풀어보았습니다. 그래프 탐색 알고리즘의 중요성과 BFS의 활용을 통해 여러 문제를 해결할 수 있는 능력을 키우시길 바랍니다. 문제를 풀면서 직면한 어려움을 해결하고, 다양한 해법을 시도해보는 것이 알고리즘 실력을 향상시키는 데 큰 도움이 됩니다.

앞으로도 다양한 알고리즘 문제를 다루며, 이론과 실전 코딩이 결합된 교육 콘텐츠를 제공할 예정입니다. 많은 관심과 응원 부탁드립니다.

© 2023 알고리즘 강좌. 모든 권리 보유.

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

작성자: 조광형

작성일: 2024년 11월 26일

소개

본 강좌에서는 C# 프로그래밍 언어를 사용하여 ‘최소 공통 조상(LCA; Lowest Common Ancestor)’ 문제를 해결하는 방법에 대해 다룰 것입니다.
최소 공통 조상은 이진 트리에서 두 노드의 공통 조상 중 가장 깊은 노드를 의미합니다.
특정 알고리즘 및 자료 구조를 활용하여 이 문제를 해결하는 과정을 자세히 설명하겠습니다.

문제 설명

주어진 이진 트리와 두 개의 노드 A, B에 대해, A와 B의 최소 공통 조상을 찾는 문제입니다.
이진 트리는 비어있지 않으며, 모든 노드는 고유한 값을 갖습니다.
추가적으로 노드 A와 B는 반드시 트리에 존재한다고 가정합니다.

입력 형식

                - 첫 번째 줄: 트리의 노드 수 N (1 ≤ N ≤ 10^5)
                - 두 번째 줄: N개의 공백으로 구분된 정수로 트리의 노드 값
                - 세 번째 줄: 두 개의 정수 A, B (A, B는 트리의 노드에 존재)
            

출력 형식

                - A와 B의 최소 공통 조상의 값
            

문제 예시

                입력:
                7
                3 5 1 6 2 0 8
                5 1

                출력:
                3
            

해결 방법

최소 공통 조상을 찾기 위한 여러 방법이 있지만, 일반적으로 트리의 깊이를 고려하면서
DFS(Depth First Search) 방식으로 탐색하는 방법이 효과적입니다.
이진 탐색 트리(BST)의 경우에는 A와 B가 각각 어느 서브트리에 속하는지를 판단하여
조상을 효율적으로 찾는 방법이 가능합니다. 하지만 일반적인 이진 트리에서는 부모 노드의 정보를 활용하는
방식이 필요합니다.

알고리즘 설계

알고리즘의 주요 단계는 다음과 같습니다.

  1. 입력으로 주어진 트리를 생성합니다.
  2. 각 노드에 대한 부모 노드를 맵 또는 배열로 저장합니다.
  3. 노드 A에서 루트까지의 경로를 기록합니다.
  4. 노드 B에서 루트까지의 경로를 기록합니다.
  5. 두 경로에서 첫 번째 공통 노드를 찾습니다.

C# 코드 구현

아래는 위의 알고리즘을 C#으로 구현한 코드입니다.

                
using System;
using System.Collections.Generic;

class TreeNode {
    public int Value;
    public TreeNode Left, Right;
    
    public TreeNode(int value) {
        this.Value = value;
        this.Left = null;
        this.Right = null;
    }
}

class Program {
    static Dictionary nodeMap = new Dictionary();
    static TreeNode root;

    public static void Main(string[] args) {
        int N = Convert.ToInt32(Console.ReadLine());
        int[] values = Array.ConvertAll(Console.ReadLine().Split(), int.Parse);
        int A = Convert.ToInt32(Console.ReadLine());
        int B = Convert.ToInt32(Console.ReadLine());

        BuildTree(values, 0, N);
        TreeNode ancestor = FindLCA(root, A, B);
        Console.WriteLine(ancestor.Value);
    }

    static TreeNode BuildTree(int[] values, int index, int N) {
        if (index < N) {
            TreeNode node = new TreeNode(values[index]);
            nodeMap[values[index]] = node;
            node.Left = BuildTree(values, 2 * index + 1, N);
            node.Right = BuildTree(values, 2 * index + 2, N);
            return node;
        }
        return null;
    }

    static TreeNode FindLCA(TreeNode node, int A, int B) {
        if (node == null) return null;
        if (node.Value == A || node.Value == B) return node;

        TreeNode leftLCA = FindLCA(node.Left, A, B);
        TreeNode rightLCA = FindLCA(node.Right, A, B);

        if (leftLCA != null && rightLCA != null) return node;
        return (leftLCA != null) ? leftLCA : rightLCA;
    }
}
                
            

코드 설명

1. TreeNode 클래스: 이진 트리의 노드를 표현합니다. 각각의 노드는 값과 왼쪽, 오른쪽 자식을 갖습니다.

2. Main 메서드: 프로그램의 시작점입니다. 사용자로부터 입력을 받고 트리를 생성한 후 LCA를 찾습니다.

3. BuildTree 메서드: 배열을 사용하여 이진 트리를 구축합니다. 재귀적으로 호출되어 트리를 구성합니다.

4. FindLCA 메서드: 재귀적으로 트리를 탐색하며 두 노드 A와 B의 최소 공통 조상을 찾습니다.

시간 복잡도

본 알고리즘의 시간 복잡도는 O(N)입니다.
트리를 한 번 순회하면서 각 노드를 방문하므로 노드 수에 비례하여 증가합니다.
공간 복잡도 또한 O(N)인데, 이는 재귀 호출 스택과 노드 정보를 저장하는 데 사용되는 공간 때문입니다.

맺음말

이번 강좌에서는 C#을 이용하여 최소 공통 조상 문제를 해결하는 방법에 대해 배웠습니다.
알고리즘의 원리를 이해하고 코드를 구현하면서, 코딩 테스트에서 이와 같은 트리 문제를 효과적으로 해결하는
방법에 대한 감을 잡을 수 있었을 것입니다.
다음 시간에는 다른 데이터 구조나 알고리즘 문제를 깊이 있게 다뤄보도록 하겠습니다.
감사합니다.

C# 코딩테스트 강좌, 회의실 배정하기

이 강좌에서는 C#을 사용하여 회의실 배정 문제를 해결하는 방법을 단계별로 설명합니다. 회의실 배정 문제는 알고리즘적 사고를 증진시키며, 여러 코딩 테스트에서 자주 출제되는 주제이기도 합니다.

문제 정의

다음과 같이 회의의 시작시간과 종료시간이 주어집니다. 각 회의는 특정한 시작시간과 종료시간을 가지고 있으며, 회의는 동시에 진행될 수 없습니다. 주어진 모든 회의를 수용할 수 있는 최소한의 회의실 개수를 구하는 문제입니다.

입력

    첫 번째 줄에 회의의 개수 N이 주어집니다. (1 ≤ N ≤ 105)
    다음 N개 줄에는 각각의 회의의 시작 시간 start와 종료 시간 end가 공백으로 구분되어 주어집니다. (0 ≤ start < end ≤ 106)
    

출력

회의실의 개수를 출력합니다.

예제

    입력 예시:
    3
    0 30
    5 10
    15 20

    출력 예시:
    2
    

문제 해결 접근 방법

이 문제를 해결하기 위해서는 다음과 같은 접근 방법을 사용할 수 있습니다:

  1. 우선 모든 회의의 시작시간과 종료시간을 기준으로 정렬합니다.
  2. 각 회의를 차례대로 확인하며, 현재 사용 중인 회의실의 종료시간과 비교하여 새로운 회의를 시작할 수 있는지 결정합니다.
  3. 새로운 회의가 시작될 수 있다면, 해당 회의실의 종료시간을 갱신하고, 그렇지 않다면 새로운 회의실을 사용할 준비를 합니다.
  4. 마지막으로 사용한 회의실의 개수를 반환합니다.

C# 코드 구현


using System;
using System.Collections.Generic;
using System.Linq;

class Program {
    static void Main() {
        int n = int.Parse(Console.ReadLine());
        List<(int start, int end)> meetings = new List<(int start, int end)>();

        for (int i = 0; i < n; i++) {
            var parts = Console.ReadLine().Split(' ');
            int start = int.Parse(parts[0]);
            int end = int.Parse(parts[1]);
            meetings.Add((start, end));
        }

        // 회의 시간을 종료 시간 기준으로 정렬
        meetings.Sort((a, b) => a.end.CompareTo(b.end));

        // 회의실 개수를 세기 위한 변수
        int roomCount = 0;
        PriorityQueue minHeap = new PriorityQueue(); // 우선순위 큐 사용

        foreach (var meeting in meetings) {
            // 현재 회의 시작 시간이 가장 먼저 끝나는 회의실의 종료시간보다 크거나 같으면
            if (minHeap.Count > 0 && minHeap.Peek() <= meeting.start) {
                minHeap.Dequeue(); // 회의실 재사용
            }
            // 새로운 회의실 사용
            minHeap.Enqueue(meeting.end, meeting.end);
        }

        roomCount = minHeap.Count; // 남아있는 회의실 수
        Console.WriteLine(roomCount);
    }
}
    

해설

위의 알고리즘은 대체로 그리디 알고리즘에 해당합니다. 각 회의를 경과하며 현재 종료되는 회의실을 체크하여 최소한의 회의실을 사용하도록 설정합니다. 우선순위 큐를 사용하여 현재 사용 중인 회의실들을 효율적으로 관리합니다. 이 알고리즘은 다음과 같은 절차로 통하여 최적의 결과를 도출합니다:

시간복잡도 분석

알고리즘의 주된 Time Complexity는 O(N log N)입니다. 이는 회의의 리스트를 정렬하는 데 소요되는 시간입니다. 이후 각 회의를 확인하는 과정은 O(N)에 해당하므로 총 시간복잡도는 O(N log N)이 됩니다.

공간복잡도 분석

공간복잡도는 O(N)입니다. 유지하는 리스트와 우선순위 큐에 회의 정보를 저장하기 때문입니다.

결론

이번 강좌에서는 C#을 이용한 회의실 배정 문제의 해결 방식을 살펴보았습니다. 이 문제를 풀며 그리디 알고리즘의 적절한 활용법과 우선순위 큐의 효율적인 사용법을 배울 수 있었습니다. 다양한 회의실 배정 문제가 있을 수 있으니 연습을 통해 더 많은 문제를 해결해보길 바랍니다.

C# 코딩테스트 강좌, 불우이웃돕기

안녕하세요! 오늘은 C#을 사용하여 불우이웃돕기와 관련된 알기 쉬운 알고리즘 문제를 해결해보는 시간을 가지겠습니다. 이 강좌에서는 문제 정의와 함께, 문제 해결을 위한 접근 방식, 알고리즘 구현, 그리고 최적화된 코드까지 다양한 측면을 다룰 것입니다.

문제 정의

문제: 불우이웃돕기 기부 목표 달성하기

기부 목표가 설정되어 있을 때, 여러 사람들이 기부를 통해 이 목표를 달성할 수 있는지 여부를 판단하는 문제입니다.

입력:

  • goal: 기부 목표 금액 (정수)
  • donations: 기부자들의 기부 내역 (정수 배열)

출력:

  • 기부 목표를 달성할 수 있으면 true, 그렇지 않으면 false 반환

문제 해결 접근법

이 문제는 주어진 기부자들의 기부금으로 목표 금액에 도달할 수 있는지를 확인하는 것이 핵심입니다. 우리는 다음과 같은 방법으로 문제를 해결할 수 있습니다:

  1. 기부자들이 기부한 금액의 합계를 계산한다.
  2. 합계가 목표 금액보다 크거나 같으면, 목표를 달성한 것이므로 true를 반환한다.
  3. 그렇지 않으면 false를 반환한다.

알고리즘 구현

이제 위의 접근방법을 C# 코드로 구현해보겠습니다.

using System;

class Program
{
    static void Main(string[] args)
    {
        int goal = 100000; // 목표 금액
        int[] donations = { 25000, 30000, 50000, 35000 }; // 기부자 기부 내역

        bool result = CanAchieveGoal(goal, donations);
        Console.WriteLine(result ? "목표를 달성했습니다!" : "목표를 달성하지 못했습니다.");
    }

    static bool CanAchieveGoal(int goal, int[] donations)
    {
        int total = 0;
        foreach (var donation in donations)
        {
            total += donation;
        }
        return total >= goal;
    }
}

코드 분석

위 코드는 먼저 목표 금액과 기부자의 기부 내역을 정의합니다. 그리고 CanAchieveGoal 함수를 통해 기부의 총합을 계산하고, 목표 금액과 비교하여 최종 결과를 출력합니다. 이 코드는 간단하면서도 이해하기 쉽습니다.

성능 최적화

현재 코드는 기부 내역의 길이에 따라 O(n) 시간 복잡도를 가집니다. 이 경우, 기부자가 많아진다고하여 성능상의 문제는 없습니다. 그러나 추가적인 최적화가 필요할 경우, 예를 들어 목표 금액에 도달했음을 조기에 확인할 수 있는 방법이 있습니다. 즉, 총액이 목표에 도달하면 더 이상 반복문을 진행하지 않도록 할 수 있습니다.

static bool CanAchieveGoal(int goal, int[] donations)
{
    int total = 0;
    foreach (var donation in donations)
    {
        total += donation;
        if (total >= goal)
        {
            return true; // 조기에 목표 달성을 확인
        }
    }
    return false;
}

결론

이번 글에서는 C#을 사용하여 불우이웃돕기와 관련된 기부 목표 달성 여부를 판단하는 알고리즘 문제를 해결해보았습니다. 다양한 접근 방법과 구현 코드, 성능 최적화에 대한 내용까지 모두 살펴보았습니다. 이와 같은 문제들은 실전 코딩테스트에서 자주 출제되므로, 충분한 연습이 필요합니다.

마지막으로, 이 강좌를 통해 기부와 관련된 소중한 가치와, C# 프로그래밍의 기초적인 부분을 함께 배우셨으면 합니다. 다음 강좌에서도 더 많은 문제를 가지고 찾아뵙겠습니다. 감사합니다!