C# 코딩테스트 강좌, 어떤 알고리즘으로 풀어야 할까

코딩테스트는 현대의 IT 기업에서 필수적인 절차입니다. 많은 지원자들이 알고리즘과 자료구조를 이해하고, 이를 바탕으로 문제를 해결하는 능력을 평가받기 위해 준비하고 있습니다. 이 글에서는 C#을 활용하여 알고리즘 문제를 해결하는 방법에 대해 자세히 살펴보겠습니다.

문제: 유효한 괄호 문자열

주어진 문자열이 유효한 괄호 문자열인지 판단하는 문제입니다. 문자열은 ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ 및 ‘]’ 문자로만 구성됩니다. 유효한 괄호 문자열은 다음 조건을 만족해야 합니다:

  • 모든 열린 괄호는 닫힌 괄호로 닫혀야 합니다.
  • 닫힌 괄호는 항상 그에 해당하는 최근의 열린 괄호와 짝을 이루어야 합니다.
  • 괄호의 닫힘이 열림 이전에 발생하지 않아야 합니다.

예를 들어, 문자열 “{[()]}”는 유효한 괄호 문자열이며, “{[(])}”는 유효하지 않습니다.

문제 해결 과정

1. 문제 분석

이 문제를 해결하기 위해서 스택 자료구조를 사용할 수 있습니다. 스택은 후입선출(LIFO) 방식으로 작동하므로, 열린 괄호를 스택에 추가하고, 닫힌 괄호를 만날 경우 스택에서 가장 최근의 열린 괄호를 제거하는 방식으로 진행하면 됩니다. 모든 괄호를 확인한 후 스택이 비어 있다면 올바른 괄호 문자열입니다.

2. 알고리즘 설계

이 문제를 해결하기 위한 알고리즘은 다음과 같습니다:

  1. 괄호의 짝을 정의한다: { '(': ')', '{': '}', '[': ']' }
  2. 문자열을 순회하면서 열린 괄호는 스택에 추가한다.
  3. 닫힌 괄호를 만났을 경우, 스택에서 최근 열린 괄호를 꺼내서 짝이 맞는지 확인한다.
  4. 스택이 비어 있지 않거나 짝이 맞지 않는 경우, 유효하지 않은 문자열로 판단한다.
  5. 문자열을 모두 탐색 이후, 스택이 비어 있으면 유효한 문자열이다.

3. C# 코드 구현


using System;
using System.Collections.Generic;

public class ValidParentheses
{
    public static bool IsValid(string s)
    {
        Stack stack = new Stack();
        Dictionary parentheses = new Dictionary()
        {
            {'(', ')'},
            {'{', '}'},
            {'[', ']'}
        };

        foreach (char c in s)
        {
            // 열린 괄호일 경우 스택에 추가
            if (parentheses.ContainsKey(c))
            {
                stack.Push(c);
            }
            else
            {
                // 닫힌 괄호일 경우 스택 비어있으면 검증 실패
                if (stack.Count == 0 || parentheses[stack.Pop()] != c)
                {
                    return false;
                }
            }
        }

        // 스택이 비어있다면 유효한 괄호 문자열
        return stack.Count == 0;
    }
}

class Program
{
    static void Main()
    {
        string input = "{[()]}";
        bool result = ValidParentheses.IsValid(input);
        Console.WriteLine($"\"{input}\"는 유효한 괄호 문자열인가? {result}");
    }
}
    

4. 알고리즘 분석

위 코드는 스택을 사용하여 문자열을 한 번 순회하는 방식으로, 시간 복잡도는 O(n)입니다. n은 문자열의 길이입니다. 공간 복잡도 역시 O(n)입니다. 스택에 최대 n개의 열린 괄호가 쌓일 수 있기 때문입니다. 이 알고리즘은 입력 문자열이 길어져도 효율적으로 동작할 수 있습니다.

마무리

이번 글에서는 C#을 사용하여 유효한 괄호 문자열 문제를 해결하는 방법에 대해 알아보았습니다. 알고리즘 문제를 풀 때는 문제를 잘 분석하고, 적절한 자료구조를 선택하는 것이 중요합니다. 스택과 같은 간단한 자료구조를 활용하면 복잡한 문제도 효율적으로 해결할 수 있습니다. 더욱 다양한 문제를 통해 알고리즘적 사고를 키우는 데 도움이 되길 바랍니다.

C# 코딩테스트 강좌, 물의 양 구하기

문제 설명

특정 지역에 물을 담을 수 있는 수조가 있습니다. 수조의 각 벽은 높이가 다릅니다. 비가 내리면 물이 담기게 될텐데,
각 벽 사이에 얼마나 많은 물이 담길 수 있는지를 구하는 문제입니다.

입력

첫 번째 줄에는 정수 N(1 ≤ N ≤ 100,000)이 주어집니다. N은 벽의 개수를 나타냅니다.
두 번째 줄에는 N개의 정수 h_i (1 ≤ h_i ≤ 1,000,000)가 주어지며,
이는 각 벽의 높이를 나타냅니다.

출력

한 줄에 물이 담길 수 있는 양을 출력합니다.

문제 예제

예제 입력:
5
0 1 0 2 1 0 1 3 2 1 2 1
예제 출력:
6

문제 풀이 과정

이 문제를 풀기 위해서 우리는 두 개의 배열을 사용할 것입니다.
하나는 현재 위치에서 왼쪽에 있는 벽의 최고 높이를 저장하고, 또 하나는 오른쪽에 있는 벽의 최고 높이를 저장합니다.

1. 문제 분석

각각의 위치에서 담길 수 있는 물의 양은, 그 위치의 벽 높이보다 왼쪽과 오른쪽의 벽 높이 중 최소값에서 현재 벽 높이를 뺀 값입니다.
예를 들어, 위치 i에서 물의 양은 다음과 같이 계산할 수 있습니다:
water[i] = max(0, min(left_max[i], right_max[i]) - height[i])
여기서 left_max는 i의 왼쪽 벽 중 최대 높이를, right_max는 오른쪽 벽 중 최대 높이를 의미합니다.

2. 알고리즘 설계

우리는 다음과 같은 단계로 알고리즘을 설계할 수 있습니다.

  1. 입력을 받아온다.
  2. 왼쪽 벽의 최대 높이를 저장하는 배열 left_max를 생성한다.
  3. 오른쪽 벽의 최대 높이를 저장하는 배열 right_max를 생성한다.
  4. 각 위치에서 담길 수 있는 물의 양을 계산한다.
  5. 최종적으로 물의 총량을 출력한다.

3. 코드 구현

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


using System;

class Program
{
    static void Main(string[] args)
    {
        int N = int.Parse(Console.ReadLine());
        int[] height = Array.ConvertAll(Console.ReadLine().Split(), int.Parse);

        // 왼쪽 최대 높이 배열
        int[] left_max = new int[N];
        left_max[0] = height[0];

        for (int i = 1; i < N; i++)
        {
            left_max[i] = Math.Max(left_max[i - 1], height[i]);
        }

        // 오른쪽 최대 높이 배열
        int[] right_max = new int[N];
        right_max[N - 1] = height[N - 1];

        for (int i = N - 2; i >= 0; i--)
        {
            right_max[i] = Math.Max(right_max[i + 1], height[i]);
        }

        // 물의 양 계산
        int water = 0;
        for (int i = 0; i < N; i++)
        {
            water += Math.Max(0, Math.Min(left_max[i], right_max[i]) - height[i]);
        }

        Console.WriteLine(water);
    }
}
    

4. 코드 설명

위의 C# 코드에서 사용된 알고리즘은 다음과 같습니다.

  • int N = int.Parse(Console.ReadLine());
    – N값을 입력받아 벽의 개수를 저장합니다.
  • int[] height = Array.ConvertAll(Console.ReadLine().Split(), int.Parse);
    – 높이 입력을 받아 배열로 변환합니다.
  • left_max 배열은 왼쪽에서부터의 최대 높이를 저장합니다. 이를 위해 첫 번째 벽의 높이를 세팅한 후, 반복문을 돌며
    각 벽의 높이를 비교하여 최대값을 저장합니다.
  • right_max 배열도 마찬가지로 오른쪽에서부터 최대 높이를 저장합니다.
  • 마지막으로, 각 벽 위치에서 물이 담길 수 있는 양을 계산하여 총 물의 양을 구합니다.

5. 복잡도 분석

이 알고리즘의 시간 복잡도는 O(N)입니다. 벽의 개수 N에 비례하여 연산을 수행하므로 제공된
입력 범위 내에서 효율적으로 동작합니다. 공간 복잡도 또한 O(N)으로 두 개의 배열을 저장하기 위한 공간이 필요합니다.

결론

이번 글에서는 물의 양을 계산하는 문제를 C#으로 해결해 보았습니다. 알고리즘을 단계적으로 설계하고
구현하는 과정에서 코딩 테스트에서 자주 접할 수 있는 문제 유형들을 확인할 수 있었습니다.
이 강좌가 당신의 코딩 테스트 준비에 도움이 되기를 바랍니다.

C# 코딩테스트 강좌, 구간 곱 구하기

문제 설명

주어진 정수 배열 nums와 쿼리 배열 queries가 있습니다. 각 쿼리는 두 정수 (l, r)로 이루어져 있으며, 이는 nums[l] * nums[l + 1] * ... * nums[r]의 값을 구하라는 요청입니다. 단, nums 배열의 크기는 최대 10^5, queries의 길이는 최대 10^4입니다. 이때, 곱셈의 결과가 매우 커질 수 있으므로, 결과는 10^9 + 7로 나눈 나머지를 출력해야 합니다.

입력 형식

첫 번째 줄에 배열의 크기 n과 쿼리의 수 q가 주어집니다. 두 번째 줄에 n개의 정수로 이루어진 배열 nums가 주어지고, q개의 줄에 걸쳐 각 쿼리 lr가 주어집니다.

출력 형식

각 쿼리에 대해 계산한 결과를 한 줄에 하나씩 출력합니다.

예시

입력
5 3
2 3 4 5 6
0 2
1 3
2 4
출력
24
60
120

문제 풀이

이 문제를 해결하기 위해서는 구간 곱을 빠르게 계산할 수 있는 방법이 필요합니다. 단순히 각 쿼리에 대해 구간을 순회하면서 곱을 계산하면 시간 복잡도 O(q * n)에 이르게 되어, 최악의 경우 10^9의 연산을 수행할 수 있습니다. 이는 실용적이지 않기 때문에, Prefix Product를 사용하여 시간 복잡도를 줄이는 전략을 사용합니다.

Prefix Product 방법

우선, nums 배열의 원소들에 대한 누적 곱을 저장하여 쿼리의 결과를 빠르게 계산할 수 있도록 합니다.

C#
int mod = 1000000007;
int[] prefixProduct = new int[n + 1];
prefixProduct[0] = 1;

for (int i = 1; i <= n; i++) {
    prefixProduct[i] = (int)((1L * prefixProduct[i - 1] * nums[i - 1]) % mod);
}

여기서 prefixProduct[i]nums[0] * nums[1] * ... * nums[i - 1]의 값을 저장합니다. 이 구조를 통해 우리는 구간 곱을 쉽고 빠르게 계산할 수 있습니다.

구간 곱 계산하기

이제 각 쿼리 (l, r)에 대해, 구간 곱 nums[l] * nums[l + 1] * ... * nums[r]는 다음과 같이 구할 수 있습니다:

C#
int result = (int)((1L * prefixProduct[r + 1] * modInverse(prefixProduct[l]) % mod) % mod);

여기서 modInverse 함수는 모듈로 연산에 대한 역원을 구하는 함수입니다. 역원은 다음과 같이 구현할 수 있습니다:

C#
int modInverse(int a) {
    return power(a, mod - 2);
}

int power(int x, int y) {
    int res = 1;
    while (y > 0) {
        if ((y & 1) == 1) {
            res = (int)((1L * res * x) % mod);
        }
        y >>= 1;
        x = (int)((1L * x * x) % mod);
    }
    return res;
}

최종 코드

위의 설명을 토대로 전체 코드를 작성하면 다음과 같습니다:

C#
using System;

class Program {
    static int mod = 1000000007;

    static void Main() {
        string[] firstLine = Console.ReadLine().Split();
        int n = int.Parse(firstLine[0]);
        int q = int.Parse(firstLine[1]);
        int[] nums = Array.ConvertAll(Console.ReadLine().Split(), int.Parse);
        int[] prefixProduct = new int[n + 1];
        prefixProduct[0] = 1;

        for (int i = 1; i <= n; i++) {
            prefixProduct[i] = (int)((1L * prefixProduct[i - 1] * nums[i - 1]) % mod);
        }

        for (int i = 0; i < q; i++) {
            string[] query = Console.ReadLine().Split();
            int l = int.Parse(query[0]);
            int r = int.Parse(query[1]);
            int result = (int)((1L * prefixProduct[r + 1] * modInverse(prefixProduct[l]) % mod) % mod);
            Console.WriteLine(result);
        }
    }

    static int modInverse(int a) {
        return power(a, mod - 2);
    }

    static int power(int x, int y) {
        int res = 1;
        while (y > 0) {
            if ((y & 1) == 1) {
                res = (int)((1L * res * x) % mod);
            }
            y >>= 1;
            x = (int)((1L * x * x) % mod);
        }
        return res;
    }
}

결론

이번 강좌에서는 배열의 구간 곱을 효과적으로 계산하기 위해 Prefix Product 방법과 모듈로 연산의 역원의 개념을 활용하는 방법을 학습했습니다. 이 방법을 응용하면 더욱 복잡한 쿼리 처리에도 유용하게 활용할 수 있을 것입니다. C# 언어의 특징을 살려 효율적인 코드를 작성하고, 알고리즘 문제 해결 능력을 한층 더 향상시킬 수 있습니다.

추가적으로, 이 문제에서 사용한 Prefix Product 기법은 다양한 형태의 쿼리 문제에서 유용하게 적용될 수 있으므로, 다양한 문제에 적용해보는 것도 좋습니다. 감사합니다!

C# 코딩테스트 강좌, 계단 수 구하기

1. 문제 설명

계단 수는 특정 조건에 맞는 수를 세는 문제입니다.
주어진 수 N이 있을 때, N 자리의 계단 수를 구하는 문제를 다루겠습니다.
계단 수란 각 자리의 수가 그 다음 자리 수보다 1만큼 크거나 작아야 하는 수입니다. 예를 들어, 1234, 3210은 계단 수입니다.
그러나 1123, 2222는 계단 수가 아닙니다.
이 문제를 통해 동적 프로그래밍의 기본 아이디어와 구현 방법을 이해할 수 있습니다.

2. 입력 & 출력 형식

입력

N (1 ≤ N ≤ 100), 주어진 자연수인 N은 계단 수의 자리 수를 나타냅니다.

출력

N자리 계단 수의 개수를 1,000,000,000으로 나눈 나머지를 출력합니다.

3. 문제 접근 방법

계단 수를 세기 위해서는 동적 프로그래밍(Dynamic Programming) 방식을 선택해야 합니다.
문제를 풀기 위해 다음과 같은 방법으로 접근할 수 있습니다.
N자리 계단 수를 구하기 위해, (N-1)자리 계단 수를 이용하여 값을 계산합니다.
다음과 같은 규칙을 찾아낼 수 있습니다:

  • 각 자리 수가 0이면, 다음 자리 수는 1만 올 수 있습니다.
  • 각 자리 수가 9이면, 다음 자리 수는 8만 올 수 있습니다.
  • 각 자리 수가 1~8인 경우, 다음 자리 수는 그보다 1 작거나 큰 두 가지 선택이 가능합니다.

이를 바탕으로 DP 테이블을 구성하고, 각 자리수에 따른 계단 수를 기록해 나가면서
최종적으로 N자리 계단 수를 구할 수 있습니다.
DP 배열을 초기화하고, 점차적으로 채워나가는 방식으로 접근합니다.

4. C# 코드 구현

이제 본격적으로 C#으로 계단 수를 구하는 코드를 구현해 보겠습니다.
아래는 문제 해결을 위한 C# 코드입니다.


using System;

class Program
{
    static void Main()
    {
        int N = int.Parse(Console.ReadLine());
        long[,] dp = new long[N + 1, 10];

        // 1자리 계단 수 초기화 (1~9)
        for (int i = 1; i <= 9; i++)
        {
            dp[1, i] = 1;
        }

        // DP를 활용하여 N자리 계단수 구하기
        for (int i = 2; i <= N; i++)
        {
            for (int j = 0; j <= 9; j++)
            {
                if (j == 0)
                {
                    dp[i, j] = dp[i - 1, j + 1] % 1000000000; // 0 -> 1
                }
                else if (j == 9)
                {
                    dp[i, j] = dp[i - 1, j - 1] % 1000000000; // 9 -> 8
                }
                else
                {
                    dp[i, j] = (dp[i - 1, j - 1] + dp[i - 1, j + 1]) % 1000000000; // j-1, j+1
                }
            }
        }

        // N자리 계단수의 총합 계산
        long result = 0;
        for (int j = 0; j <= 9; j++)
        {
            result = (result + dp[N, j]) % 1000000000;
        }

        // 결과 출력
        Console.WriteLine(result);
    }
}
            

5. 코드 설명

위의 코드를 단계별로 설명하겠습니다.
1. 우선, 사용자의 입력으로부터 N을 읽어옵니다.
2. 2차원 배열 dp를 생성하여 dp[i, j]는 i자리의 계단 수 중 j로 끝나는 수의 개수를 저장합니다.
3. 1자리 수는 0~9 중에서 1~9만 사용 가능하므로 이를 초기화합니다.
4. 이후, 2자리부터 N자리까지 각 자리수를 계산합니다.
5. 각 자리수의 계산은 위에서 설명한 규칙에 따라 이루어집니다.
6. 마지막으로 N자리의 계단수를 모두 합산하여 결과를 출력합니다.

6. 복잡도 분석

이 알고리즘의 시간 복잡도는 O(N)입니다.
N이 100일 때, 2중 루프를 통해서 각 자리수에 대한 가능성을 확인하므로
O(N) * O(10) = O(N)에 해당합니다.
공간 복잡도는 O(N * 10)이며, 이는 메모리 사용량이 비교적 적다는 것을 의미합니다.

7. 예제

입력 예시


3
            

출력 예시


32
            

위의 예제에서 3자리 계단 수는 32개가 존재합니다.
이를 통해 문제를 이해할 수 있습니다.

8. 결론

이 강좌에서는 계단 수를 구하는 문제를 통해 동적 프로그래밍의 기초적인
개념을 익혔습니다.
계단 수 문제는 알고리즘 스킬을 향상시키는 데 도움이 되는 좋은 연습 문제가 될 수 있습니다.
다양한 문제를 풀어보면서 더 많은 경험을 쌓길 바랍니다.
앞으로도 알고리즘 문제 풀이에 도전하세요!

C# 코딩테스트 강좌, 이분 그래프 판별하기

1. 서론

그래프 이론은 컴퓨터 과학과 알고리즘의 중요한 분야로, 다양한 실제 문제를 해결하는 데 활용됩니다.
이 글에서는 ‘이분 그래프’의 개념과 이를 판별하는 알고리즘 문제를 소개하고, C#으로 이를 해결하는 방법을
자세히 설명하겠습니다. 이분 그래프란 그래프의 정점을 두 개의 집합으로 나눈 후, 같은 집합의 정점끼리는
연결되지 않도록 만들어진 그래프를 말합니다. 이러한 그래프는 여러 가지 응용 분야에서 중요한 역할을 합니다.
예를 들어, 두 종류의 객체 간의 연결 관계나 이분 매칭 문제 등에서 자주 사용됩니다.

2. 문제 설명

이 문제에서는 그래프가 주어졌을 때, 이분 그래프인지 아닌지를 판별하는 알고리즘을 구현해야 합니다.
예를 들어, 그래프의 정점 수 V와 간선 수 E가 주어지고, V개의 정점과
E개의 간선이 이어져 있습니다. 주어진 그래프가 이분 그래프일 경우에는 “YES”를 출력하고, 그렇지 않을 경우에는
“NO”를 출력해야 합니다.

입력 형식

        첫 번째 줄에는 정점의 수 V와 간선의 수 E가 주어진다.
        다음 E개의 줄에는 간선의 양 끝 점을 나타내는 두 정수가 주어진다.
    

출력 형식

        주어진 그래프가 이분 그래프일 경우 "YES", 그렇지 않을 경우 "NO"를 출력한다.
    

3. 알고리즘 접근 방식

이 문제를 해결하기 위해, 우리가 사용할 알고리즘은 BFS (너비 우선 탐색) 또는 DFS (깊이 우선 탐색)입니다.
이분 그래프 판별을 위해 각 정점을 두 개의 색으로 칠하는 방법을 사용할 것입니다.
즉, 하나의 집합은 색상 1로 표시하고 다른 집합은 색상 2로 표시합니다. 만약 인접한 두 정점이 동일한 색상으로
칠해진다면 이분 그래프가 아니라는 것을 의미합니다.

알고리즘 단계

  1. 그래프를 인접 리스트 형식으로 나타내기 위해 데이터 구조를 생성합니다.
  2. BFS 또는 DFS를 사용하여 정점을 방문하며, 정점의 색상을 결정합니다.
  3. 인접한 정점이 동일한 색상인지 체크하여 이분 그래프 여부를 판단합니다.
  4. 모든 정점을 방문하고 나서 최종 결과를 출력합니다.

4. C# 코드 구현

이제 앞서 설명한 알고리즘을 바탕으로 C#으로 코드를 구현하겠습니다.
아래는 이분 그래프 판별을 위한 C# 코드입니다.

using System;
using System.Collections.Generic;

class Program
{
    static List[] graph;
    static int[] color;

    static void Main(string[] args)
    {
        string[] input = Console.ReadLine().Split();
        int V = int.Parse(input[0]);
        int E = int.Parse(input[1]);

        graph = new List[V + 1];
        for (int i = 0; i <= V; i++)
            graph[i] = new List();

        for (int i = 0; i < E; i++)
        {
            input = Console.ReadLine().Split();
            int u = int.Parse(input[0]);
            int v = int.Parse(input[1]);
            graph[u].Add(v);
            graph[v].Add(u);
        }

        color = new int[V + 1];

        bool isBipartite = true;
        for (int i = 1; i <= V; i++)
        {
            if (color[i] == 0)
                isBipartite &= BFS(i);
        }

        Console.WriteLine(isBipartite ? "YES" : "NO");
    }

    static bool BFS(int start)
    {
        Queue queue = new Queue();
        queue.Enqueue(start);
        color[start] = 1;  // 최초 정점 색칠

        while (queue.Count > 0)
        {
            int node = queue.Dequeue();
            foreach (int neighbor in graph[node])
            {
                if (color[neighbor] == 0)  // 아직 색칠 안 된 경우
                {
                    color[neighbor] = 3 - color[node];  // 현재 정점과 다른 색
                    queue.Enqueue(neighbor);
                }
                else if (color[neighbor] == color[node])  // 인접한 두 정점 색이 같으면
                {
                    return false;
                }
            }
        }
        return true;
    }
}
    

5. 코드 설명

위 C# 코드에서 사용된 주요 구성 요소와 로직을 설명하겠습니다.

자료구조

List[] graph: 그래프를 인접 리스트로 표현하기 위해 사용됩니다.
정점 번호를 키로 하여 연결된 정점 리스트를 저장합니다.

int[] color: 각 정점의 색을 저장합니다. 0은 색칠 안 된 상태, 1과 2는 각각의 색을 나타냅니다.

메인 메서드

– 입력으로 그래프의 정점 수와 간선 수를 받고, 이를 기반으로 그래프를 생성합니다.

– 모든 정점을 순회하며, BFS를 호출하여 이분 그래프 여부를 확인합니다.
주어진 모든 정점에 대해 확인 후 결과를 출력합니다.

DFS 메서드

– BFS 메서드는 큐를 기반으로 너비 우선 탐색을 수행합니다.
시작 정점을 색칠하고, 인접한 정점이 색칠되지 않았다면 현재 정점과 다른 색으로 색칠합니다.

– 이미 색칠된 정점이 현재 정점과 같은 색으로 색칠되어 있다면 이분 그래프가 아니므로 false를 반환합니다.

6. 복잡도 분석

본 알고리즘의 시간복잡도는 O(V + E)입니다.
V는 정점의 수, E는 간선의 수이며, 그래프의 모든 정점과 간선을 탐색하기 때문입니다.
따라서 주어진 입력 크기에 대해 효율적으로 작동할 수 있습니다.

7. 결론

이 글에서는 이분 그래프의 정의와 이를 판별하는 문제를 C#으로 해결하는 방법에 대해 설명했습니다.
그래프 이론의 기초와 BFS/DFS 알고리즘을 익히고, 이를 실전에 활용하는 데 도움이 되었기를 바랍니다.
또한, 다양한 문제를 풀어보며 그래프 알고리즘에 대한 이해를 높여가길 바랍니다.

8. 추가 참고자료