자바 코딩테스트 강좌, 임계 경로 구하기

서론

소프트웨어 개발과 관련된 많은 기업에서 코딩 테스트는 필수적인 채용 과정으로 자리 잡고 있습니다.
특히, 자바는 많은 기업에서 선호되는 프로그래밍 언어 중 하나입니다.
이번 포스트에서는 자바를 사용하여 ‘임계 경로 구하기’ 문제를 해결하는 방법에 대해 자세히 알아보겠습니다.
임계 경로는 유 directed graph에서 가장 긴 경로를 찾는 것을 의미하며, 특히 프로젝트 관리 도구에서 일정 계획을 세우는 데 중요한 역할을 합니다.

문제 설명

주어진 방향 그래프에서 각 노드는 작업을 의미하고, 각 간선은 작업 간의 의존성을 나타냅니다.
여기서 방향 그래프의 노드를 시작으로 하여 최종 노드까지 도달하는 가장 긴 경로를 찾는 것이 우리의 목표입니다.
예를 들어, 각 작업의 수행 시간도 주어졌을 때, 프로젝트 완료까지 걸리는 최대 시간을 구하라는 것이 문제의 요지입니다.

입력 형식

  • 정수 N: 작업의 개수 (1 ≤ N ≤ 10,000)
  • 정수 M: 의존성의 개수 (1 ≤ M ≤ 100,000)
  • 각 작업의 수행 시간: 배열의 형태로 주어짐
  • 의존성 정보: 배열의 형태로 주어짐 (a → b: 작업 a는 작업 b가 완료된 후에 수행 가능)

출력 형식

최종 작업을 완료하는 데 걸리는 최대 시간을 출력합니다.

예제

        **입력**:
        5 4
        [2, 3, 4, 1, 5]
        [
            (0, 1),
            (1, 2),
            (1, 3),
            (3, 4)
        ]
        
        **출력**:
        10
    

이 예제에서는 최대 경로가 2(0에서 1로) + 3(1에서 2로) + 5(3에서 4로)로 None의 연속성을 위한 것을 기반으로 한다면 최종 작업을 완료하는 데 10의 시간이 소요됩니다.

문제 접근 방법

‘임계 경로 구하기’ 문제를 해결하기 위해서는 그래프 탐색 알고리즘을 활용할 수 있습니다.
일반적으로 Topological Sort을 사용하여 작업의 순서를 정한 후, 각 노드에 대해 누적 시간을 계산하여 최종 시간을 결정하게 됩니다.
전체적인 풀이는 다음과 같은 단계로 진행됩니다:

  1. 그래프 생성: 각 작업과 그 작업에 의존적인 작업을 연결하는 방향 그래프를 생성합니다.
  2. 탑오로지 정렬: 의존성이 있는 작업을 정렬하여 순서대로 작업을 수행할 수 있도록 합니다.
  3. 시간 계산: 각 작업을 수행하는 데 걸리는 시간을 누적하여 최종 목표 작업의 시간을 계산합니다.

1단계: 그래프 생성

입력된 의존성 정보를 사용하여 그래프를 생성합니다.
각 작업을 정점으로 보고, 의존성에 대해 간선으로 연결합니다.
자바에서는 ArrayList를 사용하여 그래프를 표현할 수 있습니다.

        List> graph = new ArrayList<>();
        int[] indegree = new int[N];
        int[] time = new int[N];

        for (int i = 0; i < N; i++) {
            graph.add(new ArrayList<>());
        }

        // 의존성 정보를 기반으로 그래프 구성
        // (a, b)를 통해 a → b로 간선 추가
        for (int[] dep : dependencies) {
            graph.get(dep[0]).add(dep[1]);
            indegree[dep[1]]++;
            time[dep[0]] = taskTimes[dep[0]];
        }
    

2단계: 탑오로지 정렬

그래프가 만들어지면, 차수(indegree)가 0인 노드부터 시작하여 위상 정렬을 통해 정점을 탐색합니다.
주로 큐를 사용하여 구현할 수 있습니다.

        Queue queue = new LinkedList<>();
        for (int i = 0; i < N; i++) {
            if (indegree[i] == 0) {
                queue.offer(i);
            }
        }

        int[] dp = new int[N]; // 각 작업 완료 시간 저장
        while (!queue.isEmpty()) {
            int u = queue.poll();
            dp[u] = Math.max(dp[u], time[u]);

            for (int v : graph.get(u)) {
                indegree[v]--;
                dp[v] = Math.max(dp[v], dp[u] + time[v]);
                if (indegree[v] == 0) {
                    queue.offer(v);
                }
            }
        }
    

3단계: 시간 계산

마지막 작업까지 도달했을 때, dp 배열의 최댓값이 바로 프로젝트 완료에 걸리는 최대 시간을 나타냅니다.

        int result = 0;
        for (int t : dp) {
            result = Math.max(result, t);
        }
        System.out.println(result);
    

자바 코드

        import java.util.*;

        public class CriticalPath {
            public static void main(String[] args) {
                // 입력 처리
                Scanner sc = new Scanner(System.in);
                int N = sc.nextInt();
                int M = sc.nextInt();
                int[] taskTimes = new int[N];
                for (int i = 0; i < N; i++) {
                    taskTimes[i] = sc.nextInt();
                }
                List> graph = new ArrayList<>();
                int[] indegree = new int[N];

                for (int i = 0; i < N; i++) {
                    graph.add(new ArrayList<>());
                }

                for (int i = 0; i < M; i++) {
                    int a = sc.nextInt();
                    int b = sc.nextInt();
                    graph.get(a).add(b);
                    indegree[b]++;
                }

                // 위상 정렬 및 시간 계산
                Queue queue = new LinkedList<>();
                for (int i = 0; i < N; i++) {
                    if (indegree[i] == 0) {
                        queue.offer(i);
                    }
                }

                int[] dp = new int[N];
                while (!queue.isEmpty()) {
                    int u = queue.poll();
                    dp[u] = Math.max(dp[u], taskTimes[u]);

                    for (int v : graph.get(u)) {
                        indegree[v]--;
                        dp[v] = Math.max(dp[v], dp[u] + taskTimes[v]);
                        if (indegree[v] == 0) {
                            queue.offer(v);
                        }
                    }
                }

                // 최대 시간 계산
                int result = Arrays.stream(dp).max().getAsInt();
                System.out.println(result);
            }
        }
    

결론

이번 강좌에서는 자바를 이용한 임계 경로 구하기 문제를 해결하는 과정을 다뤘습니다.
그래프 이론을 활용하여 각 작업의 의존성을 나타내고, 최종 작업의 완료 시간을 계산하는 방법을 배웠습니다.
이처럼 알고리즘 문제를 해결하는 과정에서 단계적인 접근이 중요하며, 이를 통해 실제 코딩 테스트에서도 좋은 성과를 낼 수 있습니다.
앞으로도 다양한 알고리즘을 다루는 글을 지속적으로 작성할 예정이니 많은 관심 부탁드립니다.

참고 자료

자바 코딩테스트 강좌, 이항계수 구하기 2

이번 강좌에서는 이항 계수(Binomial Coefficient)에 대해 자세히 알아보고, 이를 구하는 효율적인 코드를 자바로 작성해보겠습니다. 이항 계수는 조합의 수를 셀 때 사용되며, “nCr”로 표기합니다. 이 문제를 통해 알고리즘의 중요성을 이해하고, 코딩 테스트에 필요한 스킬을 향상시키는 기회를 갖게 될 것입니다.

이항계수란?

이항계수는 주어진 n과 r에 대해 r개의 원소를 n개의 원소 중에서 선택하는 방법의 수를 말합니다. 수학적으로는 다음과 같이 표현합니다:

C(n, r) = n! / (r! * (n - r)!)

여기서, n!은 n의 팩토리얼을 의미하며, n! = n × (n-1) × … × 1입니다. 이항 계수를 구하는 공식이 단순해 보일 수 있지만, n이 커지면 팩토리얼 값이 급격히 증가하므로 큰 수 계산에 유의해야 합니다.

문제 정의

다음의 문제를 해결해봅시다:

문제: 0 ≤ n ≤ 30, 0 ≤ r ≤ n 범위의 정수가 주어질 때, 이항계수 C(n, r)를 효율적으로 계산하는 자바 프로그램을 작성하시오.

문제 접근 방식

이 문제를 해결하기 위해서는 기본적인 방법부터 시작해 볼 수 있습니다. 하지만 n의 최대값이 30이므로, 단순하게 팩토리얼을 사용하는 것은 비효율적일 수 있습니다. 그러므로 우리는 동적 프로그래밍(Dynamic Programming) 기법을 활용하여 이 문제를 해결하겠습니다.

동적 프로그래밍을 이용한 이항계수 계산

동적 프로그래밍을 사용하면 중복되는 계산을 피할 수 있습니다. 이항계수를 구하는 데 있어 다음과 같은 성질을 활용합니다:

C(n, r) = C(n-1, r-1) + C(n-1, r)

이 성질을 통해, 기본 경우 C(n, 0) = C(n, n) = 1와 같은 값을 초기화하고, 나머지 값을 채워 나가는 방식으로 이항 계수를 계산할 수 있습니다.

자바 코드 구현

아래는 동적 프로그래밍을 이용하여 이항 계수를 계산하는 자바 코드입니다:

public class BinomialCoefficient {
    public static int binomialCoefficient(int n, int r) {
        int[][] dp = new int[n + 1][r + 1];

        // C(n, 0) = 1
        for (int i = 0; i <= n; i++) {
            dp[i][0] = 1;
        }

        // C(n, n) = 1
        for (int i = 0; i <= n; i++) {
            dp[i][i] = 1;
        }

        // Fill the dp table
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= Math.min(i, r); j++) {
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
            }
        }

        return dp[n][r];
    }

    public static void main(String[] args) {
        int n = 5; // 예시를 위한 값
        int r = 2; // 예시를 위한 값
        System.out.println("C(" + n + ", " + r + ") = " + binomialCoefficient(n, r));
    }
}

코드 설명

위 코드는 이항계수를 계산하기 위한 동적 프로그래밍 접근 방식입니다. 각 단계에 대한 설명은 아래와 같습니다:

  • 2D 배열 생성: 이항 계수를 저장하기 위해 (n+1) x (r+1) 크기의 2D 배열을 생성합니다.
  • 기본 경우 초기화: C(n, 0)과 C(n, n)을 초기값으로 1로 설정합니다.
  • DP 테이블 채우기: 이중 루프를 통해 각 이항 계수를 계산합니다. C(n, r) = C(n-1, r-1) + C(n-1, r) 공식에 따라 값을 채웁니다.
  • 결과 반환: 최종 결과는 dp[n][r]에 저장됩니다.

테스트 및 결과

코드를 실행하여 다양한 n과 r 값에 대해 이항계수를 확인해봅시다. 예를 들어, n=5, r=2일 때 결과가 어떻게 되는지 살펴봅시다.

C(5, 2) = 10

이는 5개의 원소 중 2개를 선택하는 경우의 수가 10임을 의미합니다. 다양한 경우에 대해 실행해보면 각 조합의 수를 확인할 수 있습니다.

복잡도 분석

이 코드의 시간 복잡도는 O(n * r)입니다. 공간 복잡도 또한 O(n * r)로, DP 테이블을 저장하기 위한 공간을 고려하면 충분히 효율적입니다. n이 30까지 가능하므로 큰 문제에서도 무리 없이 작동할 것입니다.

마무리 및 추가 학습 자료

이번 강좌에서는 이항계수를 계산하는 방법과 이를 위한 자바 코드를 작성하는 과정을 살펴보았습니다. 여기에 제시된 알고리즘은 코딩테스트에서 유용하게 사용할 수 있는 기술입니다. 이항계수에 관한 더 깊이 있는 이해를 위해서는 조합론 및 확률론에 관한 자료를 추가로 학습하는 것도 좋습니다.

앞으로도 다양한 알고리즘 문제를 풀어보며 코딩 테스트 대비와 실력을 쌓아가길 권장합니다.

자바 코딩테스트 강좌, 이항계수 구하기 1

문제 설명

이항계수는 조합론에서 두 가지를 조합하여 선택할 때 사용되는 개념으로,
C(n, k) 형식으로 표현됩니다. 이 때,
C(n, k) 는 n개의 항 중에서 k개를 선택하는 경우의 수를 의미합니다.
이 문제에서는 이항계수를 구하는 프로그램을 작성해야 합니다.

문제

두 정수 n과 k가 주어질 때, n개 중에서 k개를 뽑는 경우의 수를 출력하는 프로그램을 작성하시오.

입력

  • 첫 번째 줄에 두 정수 n(0 ≤ n ≤ 30), k(0 ≤ k ≤ n) 이 주어진다.

출력

  • 주어진 n과 k에 대한 이항계수를 출력한다.

이항계수의 정의

이항계수는 다음과 같은 수식으로 정의됩니다:

    C(n, k) = n! / (k! * (n - k)!)
    

여기서 n! (n 팩토리얼)은 1부터 n까지의 정수를 모두 곱한 값입니다.
예를 들어,
5! = 5 × 4 × 3 × 2 × 1 = 120 입니다.

문제 풀이 과정

1단계: 입출력 처리

문제의 출발점인 사용자 입력 데이터를 처리합니다.
입력으로 주어진 nk는 둘 다 정수이므로,
Scanner 클래스를 사용하여 입력을 받을 수 있습니다.

    import java.util.Scanner;

    public class BinomialCoefficient {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            int k = sc.nextInt();
            // 앞으로의 코드에서 n과 k를 이용해 이항계수를 계산
        }
    }
    

2단계: 팩토리얼 계산 함수 작성

팩토리얼을 계산하는 함수를 구현합니다.
이곳에서는 재귀 함수를 통해 n 팩토리얼을 계산하겠습니다.

    public static int factorial(int num) {
        if (num == 0) return 1; // 0! = 1
        return num * factorial(num - 1);
    }
    

3단계: 이항계수 계산 함수 작성

이항계수를 계산하는 전용 함수를 작성합니다.
앞선 단계에서 구현한 factorial 함수를 활용합니다.

    public static int binomialCoefficient(int n, int k) {
        return factorial(n) / (factorial(k) * factorial(n - k));
    }
    

4단계: 최종 코드 작성

이제 모든 기능을 모아서 최종 코드를 완성합니다.

    import java.util.Scanner;

    public class BinomialCoefficient {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            int k = sc.nextInt();
            System.out.println(binomialCoefficient(n, k));
        }

        public static int factorial(int num) {
            if (num == 0) return 1;
            return num * factorial(num - 1);
        }

        public static int binomialCoefficient(int n, int k) {
            return factorial(n) / (factorial(k) * factorial(n - k));
        }
    }
    

5단계: 시간 복잡도 분석

위의 알고리즘은 팩토리얼을 계산하는 데에 재귀를 사용하였습니다.
팩토리얼 함수의 시간 복잡도는 O(n) 입니다.
따라서 총 알고리즘의 시간 복잡도는 O(n) * 3 (팩토리얼을 세 번 호출) 이므로 O(n)으로 분석할 수 있습니다.

결론

이항계수를 구하는 방법에 대해 살펴보았습니다.
이 문제는 조합론의 기초적인 개념을 이해하는 데 도움이 되며,
실제 코딩테스트에 종종 출제되는 문제 유형입니다.
또한 재귀와 팩토리얼 개념을 활용하여 해결할 수 있어
자바 프로그래밍에 대한 이해도를 높일 수 있는 좋은 문제입니다.

다음에는 이항계수의 동적 프로그래밍을 학습하여 성능을 개선할 수 있는 방법을 다뤄보겠습니다.
이칸계수의 다양한 응용도 함께 살펴보는 시간을 갖도록 하겠습니다.

추가 참고 자료

자바 코딩테스트 강좌, 이친수 구하기

이 글에서는 이친수에 대한 문제를 풀어보겠습니다. 이친수는 0과 1로 이루어진 수열로서, 특정한 조건을 만족해야 합니다. 이 문제는 자바 코딩 테스트에서 자주 출제되는 문제 중 하나이며, 다이나믹 프로그래밍(Dynamic Programming)을 이용하여 해결할 수 있습니다.

1. 문제 정의

문제: N자리 이친수의 개수를 구하는 프로그램을 작성하세요. 이친수는 다음과 같은 조건을 만족합니다:

  • 수의 첫 자리는 1이어야 한다.
  • 0과 1로만 구성되어야 한다.
  • 1이 두 개 이상 연속되어 있으면 안 된다.

예를 들어, 3자리 이친수는 100, 101, 110 세 가지가 있습니다. 4자리 이친수는 1000, 1001, 1010, 1100, 1101가 있습니다. 이 문제를 해결하는 방법을 살펴보겠습니다.

2. 접근 방법

이 문제를 해결하기 위해 다이나믹 프로그래밍(DP) 접근법을 사용할 수 있습니다. 이친수를 N자리로 만드는 방법은 다음과 같이 구성할 수 있습니다:

2.1. 상태 정의

하나의 이친수는 마지막 자리가 0일 때와 1일 때의 두 가지 경우로 나눌 수 있습니다. 따라서 다음과 같은 두 가지 DP 배열을 정의합니다.

  • dp0[i]: 길이가 i인 이친수 중 마지막 자리가 0인 수의 개수
  • dp1[i]: 길이가 i인 이친수 중 마지막 자리가 1인 수의 개수

이때, 전체 N자리 이친수의 개수는 dp0[N] + dp1[N]로 표현할 수 있습니다. 이친수를 구성하는 규칙성을 발견하여 다음과 같이 점화식을 도출할 수 있습니다:

2.2. 점화식

  • dp0[i] = dp0[i-1] + dp1[i-1] (i-1자리 이친수 중 마지막 자리가 0인 수와 1인 수를 합산)
  • dp1[i] = dp0[i-1] (i-1자리 이친수 중 마지막 자리가 0인 수만 가능)

2.3. 초기 조건

초기값은 다음과 같습니다:

  • dp0[1] = 0 (1자리 수 중 0으로 끝나는 경우는 없음)
  • dp1[1] = 1 (1자리 수 중 1로 끝나는 경우는 하나: 1)

3. 자바 코드 구현

이제 이 조건을 이용하여 자바로 구현해보겠습니다.

public class 이친수 {
    private static int[] dp0;
    private static int[] dp1;

    public static void main(String[] args) {
        int N = 4; // 입력: N 자리
        dp0 = new int[N + 1];
        dp1 = new int[N + 1];

        // 초기값 설정
        dp0[1] = 0;
        dp1[1] = 1;

        // DP 테이블 채우기
        for (int i = 2; i <= N; i++) {
            dp0[i] = dp0[i - 1] + dp1[i - 1];
            dp1[i] = dp0[i - 1];
        }

        // 결과 출력
        int result = dp0[N] + dp1[N];
        System.out.println("N자리 이친수의 개수: " + result);
    }
}

위 코드는 이친수를 구하는 프로그램의 전체 구조를 보여줍니다. 각 단계에서 DP 테이블을 채우고, 마지막에 결과를 출력하는 방식으로 구현했습니다.

4. 성능 분석

이 알고리즘의 시간 복잡도는 O(N)이며, 공간 복잡도 역시 O(N)입니다. 이친수를 구하는 데 있어서 매우 효율적인 방법을 제공하므로, 큰 N에 대해서도 빠른 수행이 가능합니다. 이 알고리즘은 다이나믹 프로그래밍과 점화식을 잘 활용하였다는 점에서 많은 다른 비슷한 문제에서도 응용이 가능할 것입니다.

5. 문제 변형

이 문제를 변형하여 여러 가지 다른 문제를 만들어 볼 수 있습니다. 예를 들어:

  • N자리 이친수의 배열을 반환하는 프로그램
  • 이친수의 모든 가능한 조합을 출력하는 프로그램
  • 주어진 수열이 이친수인지 여부를 판단하는 프로그램

6. 결론

오늘은 이친수를 구하는 방법에 대해 다루어 보았습니다. 이를 통해 다이나믹 프로그래밍의 개념과 이 패턴을 활용한 문제 해결 능력을 향상시킬 수 있기를 바랍니다. 향후 코딩 테스트와 알고리즘 문제 풀이에서 유용하게 활용될 것입니다.

7. 참고 자료

자바 코딩테스트 강좌, 이진 트리

문제 설명

이 문제에서는 이진 트리의 각 노드에 대해 값을 저장하고, 특정 값을 가진 노드를 찾아 이진 트리에서의 높이를 반환하는 알고리즘을 작성해야 합니다. 주어진 이진 트리는 다음과 같은 형태로 정의됩니다:

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

문제: 이진 트리의 특정 값에 대한 높이 계산

주어진 이진 트리의 루트 노드와 특정 값 target이 주어진다. 이진 트리에서 target의 노드의 높이를 반환하라. 노드의 높이는 해당 노드에서 리프 노드까지의 가장 긴 경로의 길이로 정의된다. 만약 목표값을 가진 노드가 없으면 -1을 반환한다.

입력

  • TreeNode root: 이진 트리의 루트 노드
  • int target: 탐색할 값

출력

  • target의 높이, target을 찾을 수 없는 경우 -1

해결 방법

이 문제를 해결하기 위해서는 먼저 이진 트리의 높이를 계산하는 알고리즘을 이해하고 구현할 필요가 있습니다. 깊이 우선 탐색(DFS) 방식으로 트리를 탐색하면서, 목표한 값과 일치하는 노드를 찾고, 해당 노드의 높이를 반환하도록 설계합니다.

단계별 풀이

  1. 이진 트리의 구조 이해하기: 이진 트리는 각 노드가 최대 두 개의 자식 노드를 가지는 트리 구조입니다. 각 노드는 데이터를 저장하고 있습니다.
  2. 깊이 우선 탐색(DFS) 구현: 재귀적으로 데이터를 탐색하며, 목표 노드를 찾도록 합니다.
  3. 높이 계산하기: 목표 노드를 찾으면 그 노드에서 리프까지의 최대 깊이를 계산합니다.
  4. 결과 반환: 목표 노드를 찾으면 그 높이를 반환하고, 찾지 못하면 -1을 반환합니다.

자바 코드 구현

        public class Solution {
            public int findHeight(TreeNode root, int target) {
                return findNodeHeight(root, target, 0);
            }

            private int findNodeHeight(TreeNode node, int target, int depth) {
                if (node == null) {
                    return -1; // 노드가 없으면 -1 반환
                }
                
                // 현재 노드가 목표 노드인 경우
                if (node.val == target) {
                    return getHeight(node);
                }
                
                // 리프 노드가 아닐 경우 자식 노드 탐색
                int leftHeight = findNodeHeight(node.left, target, depth + 1);
                int rightHeight = findNodeHeight(node.right, target, depth + 1);

                // 왼쪽 노드에서 목표 노드를 찾았다면 그 높이를 반환
                if (leftHeight != -1) {
                    return leftHeight;
                }
                // 오른쪽 노드에서 목표 노드를 찾았다면 그 높이를 반환
                return rightHeight;
            }

            private int getHeight(TreeNode node) {
                if (node == null) return -1; // 리프 노드의 경우 깊이는 -1
                
                // 왼쪽과 오른쪽 서브트리의 깊이를 재귀적으로 계산
                int leftHeight = getHeight(node.left);
                int rightHeight = getHeight(node.right);
                
                // 현재 노드에서 리프 노드까지의 최대 깊이 반환
                return Math.max(leftHeight, rightHeight) + 1;
            }
        }
        

코드 설명

위의 코드에서 findHeight 메서드는 루트 노드와 목표값을 인자로 받아 노드를 탐색합니다. findNodeHeight는 재귀적으로 각 노드를 탐색하며 목표한 값을 찾아 그 높이를 계산합니다. getHeight 메서드는 특정 노드의 깊이를 계산하기 위해 호출됩니다.

예시

아래의 이진 트리를 고려해 보겠습니다.

                  1
                 / \
                2   3
               / \
              4   5
        

이진 트리에 대하여 target = 3일 때, 해당 노드의 높이는 0입니다. target = 2일 때 높이는 1입니다. target = 4일 때 높이는 2입니다. 존재하지 않는 노드에 대해서는 -1을 반환해야 하며, 예를 들어 target = 6일 때 결과는 -1입니다.

결론

이번 강좌에서는 이진 트리를 탐색하는 방법과 높이를 계산하는 방법에 대해 알아보았습니다. 이러한 트리 구조는 코딩 테스트에서 자주 등장하므로 잘 이해하고 연습해 두는 것이 중요합니다. 알고리즘을 연습할수록 각 문제를 해결하는 데 필요한 사고 과정이 생기고, 자신감을 갖게 될 것입니다.