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

이 글에서는 이친수에 대한 문제를 풀어보겠습니다. 이친수는 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입니다.

결론

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

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

작성자: 조광형

작성일: 2024년 11월 26일

1. 이진 탐색 알고리즘 소개

이진 탐색(Binary Search)은 정렬된 배열에서 특정 값을 찾기 위해 사용하는 알고리즘입니다. 배열의 중간값을 비교하여 원하는 값이 중간값보다 작으면 왼쪽 부분에서, 크면 오른쪽 부분에서 값을 찾는 방식으로 구현됩니다. 이진 탐색은 시간 복잡도가 O(log n)으로 매우 효율적입니다.

이진 탐색을 사용하기 위해서는 배열이 반드시 정렬되어 있어야 하며, 그렇지 않을 경우 다른 탐색 방법인 선형 탐색(Linear Search)을 사용해야 합니다.

2. 이진 탐색 문제 예제

문제: 특정 값의 인덱스 찾기

정렬된 정수 배열 nums와 정수 target가 주어질 때, target의 인덱스를 찾아 반환하는 함수 binarySearch를 작성하시오. target이 존재하지 않으면 -1을 반환해야 합니다.

예제 입력

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
target = 5
            

예제 출력

4
            

3. 문제 풀이 과정

3.1 문제 분석

이 문제는 주어진 nums 배열에서 target의 인덱스를 찾는 것이므로, 우선 배열이 정렬되어 있음을 활용하는 것이 중요합니다. 중간값을 계산하여 target과의 비교를 통해 탐색 범위를 좁혀가는 방식으로 탐색을 수행할 것입니다.

3.2 알고리즘 설계

이진 탐색 알고리즘은 다음과 같은 단계를 포함합니다:

  1. 배열의 시작 인덱스 left와 끝 인덱스 right를 초기화합니다.
  2. 중간 인덱스 mid를 계산합니다: mid = (left + right) / 2.
  3. 중간값 nums[mid]target을 비교합니다.
  4. 중간값이 target과 같으면 mid를 반환합니다.
  5. 중간값이 target보다 작으면 left = mid + 1로 업데이트하고, 그렇지 않으면 right = mid - 1로 업데이트합니다.
  6. 이 과정을 leftright보다 작거나 같을 때까지 반복합니다.
  7. 감소한 경우 target가 배열에 없음을 나타내며 -1를 반환합니다.

3.3 자바 코드 구현

위의 알고리즘을 자바로 구현해 보겠습니다.


public class BinarySearch {
    public static int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (nums[mid] == target) {
                return mid; // 존재 시 인덱스 반환
            } else if (nums[mid] < target) {
                left = mid + 1; // 오른편 탐색
            } else {
                right = mid - 1; // 왼편 탐색
            }
        }

        return -1; // 존재하지 않으면 -1 반환
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        int target = 5;
        int result = binarySearch(nums, target);
        System.out.println(result);
    }
}

            

4. 시간 복잡도 분석

이진 탐색 알고리즘의 시간 복잡도는 O(log n)입니다. 이는 매번 탐색할 때마다 배열의 크기가 절반으로 줄어들기 때문입니다. 따라서 이진 탐색은 데이터 양이 많을수록 더욱 유리한 성능을 보입니다.

5. 결론

이번 강좌에서는 이진 탐색의 개념과 이를 활용한 문제를 살펴보았습니다. 이진 탐색은 정렬된 배열에 대한 효율적인 탐색 방법으로, 다양한 코딩 테스트에서 자주 출제되는 기본적인 알고리즘입니다. 실제 코딩 테스트에 대비해 다양한 이진 탐색 문제를 풀어보며 실력을 점검하시기 바랍니다.

감사합니다!

자바 코딩테스트 강좌, 유클리드 호제법

본 블로그 글에서는 자바 코딩 테스트에서 자주 등장하는 유클리드 호제법에 대해 살펴보겠습니다. 유클리드 호제법은 두 수의 최대 공약수를 효율적으로 구할 수 있는 알고리즘으로, 기초적인 수학 지식이 필요한 문제입니다. 이 글에서는 유클리드 호제법을 이용한 알고리즘 문제를 제시하고, 그 해결 과정을 자세하게 설명하겠습니다.

문제 설명

문제: 두 정수 A와 B의 최대 공약수 구하기

두 정수 A와 B가 주어진다. 유클리드 호제법을 이용하여 두 정수의 최대 공약수(GCD)를 구하는 함수를 작성하시오.

입력:

  • 첫 줄에 두 정수 A와 B (1 ≤ A, B ≤ 100,000)가 공백으로 구분되어 주어진다.

출력:

  • 두 정수 A와 B의 최대 공약수를 한 줄에 출력한다.

유클리드 호제법 소개

유클리드 호제법은 고대 그리스의 수학자 유클리드(Euclid)가 고안한 방법으로, 주어진 두 수의 최대 공약수를 구하는 알고리즘입니다. 두 수 A와 B가 주어졌을 때, GCD(A, B)는 GCD(B, A % B)와 같다는 성질을 이용합니다. 이 과정은 A가 0이 될 때까지 반복되며, 최종적으로 B의 값이 GCD가 됩니다.

유클리드 호제법의 알고리즘

  1. A가 0이 아닐 경우: GCD(A, B) = GCD(B % A, A)
  2. A가 0일 경우: GCD(A, B) = B

위 알고리즘을 코딩에 적용하여 문제를 해결해보겠습니다.

문제 풀이

1단계: 함수를 설계하자

먼저 두 수의 최대 공약수를 구하는 함수를 설계합니다. 우리는 재귀 함수를 사용할 것입니다.

public static int gcd(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}

위 코드는 두 정수 A와 B를 인자로 받아 최대 공약수를 재귀적으로 계산합니다. B가 0일 경우 A가 최대 공약수입니다. 그 외의 경우에는 GCD(B, A % B)를 호출하여 최대 공약수를 계속 계산합니다.

2단계: 메인 함수 작성

이제 메인 함수를 작성하여 입력을 처리하고, 앞서 만든 GCD 함수를 호출하도록 합니다.

import java.util.Scanner;

public class GCDExample {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("두 정수를 입력하세요 (A B): ");
        int a = scanner.nextInt();
        int b = scanner.nextInt();

        int result = gcd(a, b);
        System.out.println("두 수 " + a + "와 " + b + "의 최대 공약수는: " + result);
    }

    public static int gcd(int a, int b) {
        if (b == 0) {
            return a;
        }
        return gcd(b, a % b);
    }
}

위 코드는 사용자로부터 두 수를 입력받아 최대 공약수를 계산하고 출력합니다. Scanner 클래스를 사용하여 입력을 받고, gcd 메서드를 호출하여 계산 결과를 얻습니다.

3단계: 코드 테스트

이제 프로그램을 실행하여 두 수의 최대 공약수를 계산해 보겠습니다. 예를 들어 48과 18을 입력하면, 다음과 같은 결과가 출력됩니다.

두 정수를 입력하세요 (A B): 48 18
두 수 48와 18의 최대 공약수는: 6

최적화 및 추가 사항

유클리드 호제법은 매우 효율적인 알고리즘으로, 시간 복잡도는 O(log(min(A, B)))입니다. 그러나 주어진 문제에서 더 다양한 활용 방법이나 성능 최적화를 고려할 수도 있습니다.

최대 공약수 배열의 활용

예를 들어, 여러 수의 배열이 있는 경우 여러 수의 최대 공약수를 구하는 방법도 생각해볼 수 있습니다. 이는 아래와 같은 형태로 구현 가능합니다.

public static int gcdArray(int[] numbers) {
    int result = numbers[0];
    for (int i = 1; i < numbers.length; i++) {
        result = gcd(result, numbers[i]);
    }
    return result;
}

위 메서드는 정수 배열을 인자로 받아, 배열 내 모든 수의 최대 공약수를 계산합니다.

결론

이번 글에서는 유클리드 호제법을 활용하여 최대 공약수를 구하는 문제를 해결해 보았습니다. 이 알고리즘은 코딩 테스트에서 빈번히 등장하며, 후보자들의 문제 해결 능력을 평가하는 데 좋은 도구가 됩니다. 입력에 대한 적절한 처리를 통해 다양한 크기의 수에 대해서도 안정적으로 동작하는 프로그램을 작성할 수 있음을 보여주었습니다.

이와 같은 알고리즘 문제를 체계적으로 정리하고 실습하는 것은 여러분이 코딩 테스트에 대비하는 데 큰 도움이 될 것입니다. 어려운 문제를 접했을 때도 유연하게 대처할 수 있도록 다양한 문제를 연습하면서 실력을 닦아 나가시기 바랍니다.

참고 자료

  • Euclidean Algorithm – Wikipedia

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

이 글에서는 ‘이분 그래프’에 대한 개념과 이를 판별하는 알고리즘 문제를 다룰 것입니다. 이분 그래프란 그래프의 정점을 두 개의 집합으로 나누어, 서로 다른 집합에 속한 정점들 사이에만 간선이 있는 그래프를 말합니다. 이러한 그래프는 다양한 문제에 응용될 수 있으며, 특히 이분 매칭과 같은 알고리즘에서 핵심적인 역할을 합니다.

1. 문제 정의

다음은 이분 그래프를 판별하는 문제의 예시입니다:


문제: 이분 그래프 판별하기

입력: 
- 첫째 줄에 정점의 수 N과 간선의 수 M이 주어진다. (1 ≤ N, M ≤ 100,000)
- 다음 M개의 줄에 각 간선의 두 정점 u와 v가 주어진다. (1 ≤ u, v ≤ N)

출력:
- 이분 그래프일 경우 "Yes", 아니면 "No"를 출력한다.

2. 이분 그래프란?

이분 그래프는 특정한 조건 아래에서 정점을 나눌 수 있는 그래프 구조입니다. 즉, 그래프의 모든 두 정점 uv가 간선으로 연결되어 있다면, uv는 서로 다른 집합에 속해야 합니다. 이러한 성질로 인해 이분 그래프는 2색으로 칠할 수 있다는 특성이 있습니다. 즉, 한 집합에 속하는 정점들은 모두 같은 색으로 칠해지고, 다른 집합에 속하는 정점들은 다른 색으로 칠해집니다.

3. 알고리즘 접근법

이분 그래프를 판별하는 방법은 다음과 같습니다. DFS(깊이 우선 탐색) 또는 BFS(너비 우선 탐색)를 사용하여 그래프를 탐색하며 각 정점에 색을 칠해 가는 방식으로 진행됩니다.

  1. 그래프를 인접 리스트로 표현합니다.
  2. 모든 정점에 대해 방문하지 않은 정점이 있다면, 해당 정점에서 BFS 또는 DFS를 시작합니다.
  3. 시작 정점에 색을 할당하고, 인접한 정점들에게는 다른 색을 할당하면서 탐색을 진행합니다.
  4. 만약 인접한 정점이 이미 방문했으며, 같은 색을 가지고 있다면 이분 그래프가 아님을 판별합니다.
  5. 모든 정점의 탐색이 끝나면 이분 그래프 여부를 판단하여 결과를 출력합니다.

4. 자바 코드 구현

아래는 위의 알고리즘을 기반으로 구현한 자바 코드입니다:


import java.util.*;

public class BipartiteGraph {
    static List> graph;
    static int[] color;
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        
        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();
            graph.get(u).add(v);
            graph.get(v).add(u);
        }
        
        color = new int[N + 1];
        boolean isBipartite = true;
        
        for (int i = 1; i <= N; i++) {
            if (color[i] == 0) {
                if (!bfs(i)) {
                    isBipartite = false;
                    break;
                }
            }
        }
        
        System.out.println(isBipartite ? "Yes" : "No");
    }
    
    private static boolean bfs(int start) {
        Queue queue = new LinkedList<>();
        queue.offer(start);
        color[start] = 1; // 시작 정점에 색칠
        
        while (!queue.isEmpty()) {
            int node = queue.poll();
            for (int neighbor : graph.get(node)) {
                if (color[neighbor] == 0) {
                    // 인접 정점이 방문하지 않았다면
                    color[neighbor] = 3 - color[node]; // 색을 반대로 칠함
                    queue.offer(neighbor);
                } else if (color[neighbor] == color[node]) {
                    // 인접 정점이 같은 색이라면
                    return false;
                }
            }
        }
        return true;
    }
}

5. 코드 설명

위의 자바 코드는 이분 그래프 판별을 위한 알고리즘을 구현한 것입니다. 각 부분을 자세히 살펴보겠습니다.

5.1 그래프 표현

그래프는 List> 형태의 인접 리스트로 표현하였습니다. 각 정점의 목록을 저장하고, 간선 정보를 추가하여 그래프 구조를 완성합니다.

5.2 색 배열

색 배열 color는 각 정점의 색을 관리합니다. 0은 방문하지 않음을, 1과 2는 서로 다른 색을 나타냅니다.

5.3 BFS 탐색

bfs 메서드는 BFS 알고리즘을 사용하여 그래프를 탐색합니다. 시작 정점을 큐에 추가하고, 방문한 정점에 색을 칠합니다. 이후 인접 정점에 대해 색을 할당하며 충돌 여부를 체크합니다. 만약 같은 색을 가진 정점이 발견되면 그 그래프는 이분 그래프가 아닙니다.

6. 시간 복잡도

이 알고리즘의 시간 복잡도는 O(N + M)입니다. 여기서 N은 정점의 수, M은 간선의 수를 의미합니다. 그래프의 모든 정점과 간선을 한 번씩 탐색하기 때문입니다.

7. 기타 고려사항

이 알고리즘은 연결 그래프와 비연결 그래프 모두에 대응할 수 있습니다. 비연결 그래프의 경우 각 연결 요소를 독립적으로 검사하여 이분 그래프 판별을 수행합니다.

8. 결론

이 글에서는 이분 그래프를 판별하는 문제를 다루었습니다. 이러한 문제는 많은 경우에 사용되며, 특히 알고리즘 면접 준비에 유용합니다. 이분 그래프에 대한 이해를 통해 더 나아가 다양한 그래프 문제를 해결하는 데 도움을 줄 것입니다. 지속적인 연습과 다양한 문제 풀이를 통해 코딩 실력을 향상시키는 것을 추천드립니다.