C++ 코딩테스트 강좌, 이친수 구하기

이 글에서는 C++을 사용하여 이친수(이진 수 중 연속된 1이 없는 수)를 구하는 방법에 대해 설명하겠습니다. 이 문제는 알고리즘 문제 해결 능력을 기르기 위한 좋은 연습이 될 것입니다.

문제 설명

이친수는 0과 1로 구성된 이진수 중에서 연속된 1이 없는 숫자를 의미합니다. 예를 들어, 0, 1, 10, 100, 101 등은 이친수입니다. 하지만 11, 111, 1100 등은 이친수가 아닙니다. 주어진 자연수 n에 대해 n 자리 이친수가 몇 개 있는지 구하는 문제입니다.

입력

첫 번째 줄에 자연수 n (1 ≤ n ≤ 45)이 주어집니다.

출력

n 자리 이친수의 개수를 출력합니다.

문제 접근 및 풀이 방법

이 문제를 해결하기 위해 피보나치 수열의 성질을 이용하도록 하겠습니다. 이친수를 만드는 방법은 다음과 같이 나눌 수 있습니다.

  • n 자리 이친수의 가장 왼쪽 비트가 0인 경우, 그 나머지 비트는 (n-1) 자리 이친수가 됩니다.
  • n 자리 이친수의 가장 왼쪽 비트가 1인 경우, 그 다음 비트는 반드시 0이어야 하므로 나머지 비트를 (n-2) 자리 이친수로 표현하게 됩니다.

즉, 이친수의 개수는 다음과 같이 정의할 수 있습니다:

f(n) = f(n-1) + f(n-2)

여기서 f(n)은 n 자리 이친수의 개수를 뜻합니다. 초기값으로는 다음과 같이 설정할 수 있습니다.

  • f(1) = 2 (0, 1)
  • f(2) = 3 (00, 01, 10)

따라서 n개의 이친수를 구하려면 피보나치 수열을 계산하는 방법을 활용하면 됩니다. 이제, C++로 실제 코드를 구현해 보겠습니다.

코드 구현


#include <iostream>

using namespace std;

long long findBinaryFriend(int n) {
    long long *dp = new long long[n + 1];
    dp[1] = 2; // 1자리 이친수
    dp[2] = 3; // 2자리 이친수

    for (int i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    return dp[n];
}

int main() {
    int n;
    cout << "자연수 n을 입력하세요 (1≤n≤45): ";
    cin >> n;

    if (n >= 1 && n <= 45) {
        cout << n << "자리 이친수의 개수는: " << findBinaryFriend(n) << "입니다." << endl;
    } else {
        cout << "잘못된 입력입니다." << endl;
    }

    return 0;
}
            

코드 설명

위 코드는 주어진 n에 대해 n 자리 이친수의 개수를 계산하는 프로그램입니다. 먼저, 동적 배열을 생성하여 메모리를 할당하고 초기값을 설정합니다. 이후 3부터 n까지 반복하면서 각 자리수에 해당하는 이친수를 찾기 위해 이전 두 항을 더하는 방식으로 진행됩니다.

마지막으로, 메인 함수에서는 사용자로부터 n을 입력받고, 조건에 맞는 경우 이친수의 개수를 출력합니다. 어떤 경우든 메모리 관리를 위해 할당한 배열을 해제해야 하는 점을 잊지 마세요.

테스트 및 검증

위 코드를 작성한 후 테스트를 해보는 것이 중요합니다. 다음과 같은 입력값에 대한 결과를 확인해 보세요:

  • n = 1: 결과는 2 (0, 1)
  • n = 2: 결과는 3 (00, 01, 10)
  • n = 3: 결과는 5 (000, 001, 010, 100, 101)
  • n = 4: 결과는 8
  • n = 5: 결과는 13

이와 같은 입력에 대해 올바른 결과가 출력되는지 확인해야 합니다. 각 n값에 대한 이친수를 직접 손으로 세어보면서 검증해보는 것이 좋습니다.

결론

이 클래스에서는 C++을 사용하여 이친수 문제를 해결하는 방법을 배웠습니다. 이 문제는 피보나치 수열과 동적 계획법의 좋은 예시입니다. 이 문제를 통해 알고리즘 문제 해결 능력을 키울 수 있으니, 여러 입력 값으로 연습해 보시기 바랍니다.

이 글이 여러분에게 도움이 되길 바랍니다. 더 많은 알고리즘을 배우고 싶다면 다른 강좌도 확인해 보세요.

C++ 코딩테스트 강좌, 이진 트리

이진 트리는 컴퓨터 과학에서 중요한 데이터 구조 중 하나로,
모든 노드가 최대 두 개의 자식 노드를 가지는 트리입니다.
이진 트리는 탐색, 정렬, 데이터 저장 등의 여러 용도로 사용되며,
알고리즘 문제에서 자주 등장합니다.
이번 강좌에서는 이진 트리를 활용한 알고리즘 문제를 풀어보겠습니다.

문제: 이진 트리의 최대 깊이 구하기

이 문제는 주어진 이진 트리를 탐색하여 그 깊이를 계산하는 것입니다.
최대 깊이란 루트 노드부터 가장 깊은 잎 노드까지의 경로의 길이를 의미합니다.
이진 트리가 비어있다면 깊이는 0입니다.
다음은 입력 및 출력 형식입니다.

입력 형식

이진 트리는 배열 형태로 주어지며, 각 노드는 다음과 같이 정의됩니다:


    struct TreeNode {
        int val;
        TreeNode* left;
        TreeNode* right;
        TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    };
    

출력 형식

이진 트리의 최대 깊이를 정수로 반환합니다.

예제


    입력: [3,9,20,null,null,15,7]
    출력: 3
    

문제 해결 접근법

이 문제를 해결하기 위해서는 이진 트리를 탐색하는 방법이 필요합니다.
우리는 주로 두 가지 방법으로 탐색할 수 있습니다:
깊이 우선 탐색(DFS) 또는 너비 우선 탐색(BFS).
이 문제의 경우 DFS를 사용하여 재귀적으로 트리를 탐색하는 방법을 선택하겠습니다.

깊이 우선 탐색 – 재귀적 방법

재귀를 통해, 트리의 각 노드를 방문하면서
왼쪽과 오른쪽 자식 노드를 계속해서 호출하여 깊이를 확인합니다.
아래는 이 과정을 나타낸 알고리즘의 핵심 아이디어입니다.

  1. 현재 노드가 NULL이면 0을 반환합니다.
  2. 왼쪽 서브트리의 깊이를 재귀적으로 호출하여 얻습니다.
  3. 오른쪽 서브트리의 깊이를 재귀적으로 호출하여 얻습니다.
  4. 왼쪽과 오른쪽 깊이 중 더 큰 값을 선택하고, 1을 추가하여 반환합니다.

구현

이제 위에서 설명한 알고리즘을 C++ 코드로 구현해보겠습니다.


    class Solution {
    public:
        int maxDepth(TreeNode* root) {
            if (root == NULL) {
                return 0;
            }
            int leftDepth = maxDepth(root->left);
            int rightDepth = maxDepth(root->right);
            return max(leftDepth, rightDepth) + 1;
        }
    };
    

코드 설명

위 코드는 C++로 작성된 이진 트리 최대 깊이 계산하는 함수입니다.
maxDepth 함수가 재귀적으로 호출되어 주어진 노드의
최대 깊이를 계산합니다.

  • if (root == NULL) 에서 노드가 NULL인지 체크합니다. NULL이면 깊이는 0이므로 반환합니다.
  • 왼쪽과 오른쪽 자식에 대해 각각의 깊이를 계산하고, 둘 중 더 큰 값을 선택하여 1을 더합니다.
  • 최종적으로 반환되는 값은 전반적인 최대 깊이입니다.

테스트

이제 몇 가지 테스트 케이스를 통해 알고리즘의 기능을 확인해보겠습니다.


    TreeNode* root = new TreeNode(3);
    root->left = new TreeNode(9);
    root->right = new TreeNode(20);
    root->right->left = new TreeNode(15);
    root->right->right = new TreeNode(7);
    
    Solution solution;
    int result = solution.maxDepth(root);
    cout << result; // 3
    

결론

이진 트리의 최대 깊이를 계산하는 알고리즘을
C++로 구현하는 방법을 살펴보았습니다.
이 문제를 통해 재귀적인 탐색의 기초를 익힐 수 있었으며,
더 복잡한 트리 관련 문제를 해결하는 데 기초가 될 것입니다.
앞으로도 이진 트리에 대해 깊이 있는 공부를 해보시길 바랍니다.
감사합니다!

C++ 코딩테스트 강좌, 이진 탐색

이진 탐색(Binary Search)은 정렬된 배열에서 특정 값을 찾는 효율적인 방법입니다. 이 알고리즘은 배열을 반복적으로 반으로 나누어 목표 값을 찾기 때문에 O(log n)의 시간 복잡도를 가집니다. 본 강의에서는 이진 탐색의 기본 개념, 구현 방법, 그리고 이를 활용한 실습 문제를 다루고자 합니다.

이진 탐색의 기본 원리

이진 탐색은 다음과 같은 절차로 동작합니다:

  1. 배열의 중앙 요소를 확인합니다.
  2. 중앙 요소와 목표 값을 비교합니다:
    • 목표 값이 중앙 요소보다 작으면 배열의 왼쪽 절반을 대상으로 검색합니다.
    • 목표 값이 중앙 요소보다 크면 배열의 오른쪽 절반을 대상으로 검색합니다.
    • 목표 값이 중앙 요소와 같으면 검색이 완료됩니다.

이 과정을 반복하여 목표 값을 찾습니다.

문제: 정렬된 배열에서 특정 값 찾기

주어진 정수 배열 arr와 정수 target이 있을 때, target의 인덱스를 반환하는 프로그램을 작성하시오. 만약 target이 없는 경우 -1을 반환해야 합니다.

입력 형식

  • 첫 번째 줄에는 배열의 크기 n이 주어집니다. (1 ≤ n ≤ 105)
  • 두 번째 줄에는 오름차순으로 정렬된 배열 arr가 주어집니다.
  • 세 번째 줄에는 찾고자 하는 숫자 target가 주어집니다.

출력 형식

타겟의 인덱스를 출력합니다. (0 이상 n-1 이하의 정수)

예제

입력:
    5
    1 3 5 7 9
    5

    출력:
    2

문제 풀이 과정

1단계: 문제 이해

먼저 문제를 정확히 이해하는 것이 중요합니다. 정렬된 배열에서 특정 요소를 빠르게 찾는 것이니, 이진 탐색을 이용해야 합니다.

2단계: 이진 탐색 알고리즘 구현

이진 탐색 알고리즘을 C++로 구현해보겠습니다.

#include <iostream>
#include <vector>

using namespace std;

int binarySearch(const vector<int> &arr, int target) {
    int left = 0;
    int right = arr.size() - 1;

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

        if (arr[mid] == target) {
            return mid;  // 타겟을 찾았다.
        } else if (arr[mid] < target) {
            left = mid + 1;  // 오른쪽 반으로 이동.
        } else {
            right = mid - 1;  // 왼쪽 반으로 이동.
        }
    }
    return -1;  // 타겟을 찾지 못했다.
}

int main() {
    int n;
    cout << "배열의 크기를 입력하세요: ";
    cin >> n;
    
    vector<int> arr(n);
    cout << "배열의 요소를 입력하세요: ";
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    int target;
    cout << "찾고자 하는 숫자를 입력하세요: ";
    cin >> target;

    int result = binarySearch(arr, target);
    if (result != -1) {
        cout << "타겟의 인덱스: " << result << endl;
    } else {
        cout << "타겟을 찾지 못했습니다." << endl;
    }

    return 0;
}

3단계: 코드 설명

binarySearch 함수는 정수 벡터와 목표 값을 입력으로 받고, 목표 값의 인덱스를 반환합니다.
leftright는 탐색 구간의 시작과 끝 인덱스를 나타냅니다.
while 루프는 leftright보다 작거나 같은 동안 실행됩니다.
– 각 반복에서 중앙 인덱스 mid를 계산하고, 중앙 요소와 목표 값을 비교하여 다음 탐색 구간을 결정합니다.

4단계: 테스트 및 검증

코드를 작성한 후, 다양한 테스트 케이스에 대해 실행하여 올바른 결과를 반환하는지 확인해야 합니다. 다음과 같은 테스트를 고려할 수 있습니다:

  • 배열에 목표 값이 존재하는 경우
  • 배열에 목표 값이 없는 경우
  • 배열의 크기가 1인 경우
  • 중복된 요소가 있는 경우

결론

이진 탐색은 매우 유용한 알고리즘입니다. 특히, 정렬된 배열에서 속도 면에서 뛰어난 성능을 발휘합니다. 본 강의에서는 이진 탐색의 기본 원리를 이해하고, 실제 코드를 작성해보았습니다. 이진 탐색을 마스터하면, 다른 많은 알고리즘 문제를 해결하는 데도 큰 도움이 될 것입니다.

다음 강의에서는 이진 탐색의 변형 문제 및 다양한 응용 사례에 대해 다룰 예정입니다. 지속적인 학습을 통해 알고리즘 능력을 향상시키시길 바랍니다!

© 2023 알고리즘 강좌 | C++ 이진 탐색

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

안녕하세요! 오늘은 이분 그래프에 대해서 알아보겠습니다. 이분 그래프는 그래프의 정점 집합을 두 개의 부분 집합으로 나눌 수 있으며, 두 집합에 포함된 정점 간에는 간선이 존재하지 않는 그래프를 의미합니다. 이 내용은 코딩테스트에서 자주 출제되는 문제 중 하나입니다. 다음 시간에는 이 문제를 풀기 위해 필요한 알고리즘과 코딩을 통해 이분 그래프를 판별하는 방법에 대해 알아보겠습니다.

문제 설명

주어진 비방향 그래프가 이분 그래프인지 판별하는 문제입니다. 한 정점에서 시작하여, 가능한 모든 정점을 방문하면서 이분 그래프 조건을 만족하는지를 확인해야 합니다.

문제 예제

다음과 같은 그래프가 주어졌을 때, 이 그래프가 이분 그래프인지 판별하시오.

정점: 6
간선: 
1 - 2
1 - 3
2 - 4
2 - 5
3 - 6

위 그래프를 보면 두 개의 집합으로 나눌 수 있는지 파악할 수 있습니다. 집합 A는 {1, 4, 5, 6}이고, 집합 B는 {2, 3}입니다. 이 그래프는 이분 그래프가 맞습니다.

문제 해결 전략

이분 그래프를 확인하기 위해 우리가 사용할 수 있는 방법 중 하나는 너비 우선 탐색(BFS) 혹은 깊이 우선 탐색(DFS)입니다. 두 방법 모두 그래프를 순회할 때 각 정점에 색깔(또는 그룹)을 부여하여 이분 그래프 조건을 확인하는 데 사용할 수 있습니다.

알고리즘 단계

  1. 그래프를 인접 리스트 형태로 표현합니다.
  2. 모든 노드를 방문할 수 있도록 반복문을 실행합니다.
  3. 현재 정점에 색칠을 하여 두 그룹으로 나눕니다.
  4. 인접한 정점이 같은 색깔인지 확인합니다.
  5. 모든 노드를 방문하였거나 갈 수 있는 노드를 모두 방문할 때까지 반복합니다.

C++ 코드 구현

이 비방향 그래프에서 이분 그래프를 판별하기 위한 C++ 코드를 살펴보겠습니다.


#include <iostream>
#include <vector>
#include <queue>

using namespace std;

const int MAX = 1000; // 정점의 최대 개수
vector<int> graph[MAX];
int color[MAX]; // 색상 설정: -1은 미방문, 0과 1은 그룹

bool isBipartite(int start) {
    queue<int> q;
    q.push(start);
    color[start] = 0; // 시작 정점에 색칠

    while (!q.empty()) {
        int v = q.front();
        q.pop();

        for (int neighbor : graph[v]) {
            // 이웃 정점이 방문하지 않았다면 색칠한다.
            if (color[neighbor] == -1) {
                color[neighbor] = 1 - color[v]; // 현재 정점과 다른 색상을 부여
                q.push(neighbor);
            }
            // 이웃 정점이 현재 정점과 같은 색이라면 이분 그래프가 아니다.
            else if (color[neighbor] == color[v]) {
                return false; // 이분 그래프 조건 위배
            }
        }
    }
    return true;
}

int main() {
    int v, e;
    cout << "정점의 개수와 간선의 개수를 입력하세요: ";
    cin >> v >> e;

    // 그래프 초기화
    for (int i = 0; i < MAX; i++) {
        color[i] = -1;
    }

    cout << "간선 입력 (형식: a b): " << endl;
    for (int i = 0; i < e; i++) {
        int a, b;
        cin >> a >> b;
        graph[a].push_back(b);
        graph[b].push_back(a); // 양방향 그래프
    }

    bool result = true;
    for (int i = 1; i <= v; i++) {
        if (color[i] == -1) {
            // 이 정점이 미방문 상태라면 이분 그래프 판별
            if (!isBipartite(i)) {
                result = false;
                break;
            }
        }
    }

    if (result) {
        cout << "이 그래프는 이분 그래프입니다." << endl;
    } else {
        cout << "이 그래프는 이분 그래프가 아닙니다." << endl;
    }

    return 0;
}

코드 설명

이 코드는 주어진 그래프의 정점과 간선을 입력받아 그 그래프가 이분 그래프인지 판단하는 과정입니다. isBipartite 함수는 모든 인접한 정점이 다른 색상으로 되어 있는지 검사합니다.

변수 및 구조체 설명

  • graph[MAX]: 그래프의 인접 리스트 표현입니다.
  • color[MAX]: 정점의 색상을 나타내는 배열입니다.
  • queue<int> q: BFS에 사용되는 큐입니다.

입력 예시 및 출력 결과

위 코드를 실행할 때 간선을 입력하는 예시는 다음과 같습니다.

정점의 개수와 간선의 개수를 입력하세요: 6 5
간선 입력 (형식: a b): 
1 2
1 3
2 4
2 5
3 6

위와 같은 입력을 주었을 때, 아래와 같은 결과를 출력합니다.

이 그래프는 이분 그래프입니다.

결론

이상으로, C++를 이용하여 이분 그래프를 판별하는 방법을 배웠습니다. BFS 또는 DFS 방식을 사용하여 문제를 해결하는 기본적인 원리를 이해했다면, 이는 다양한 그래프 문제에 적용될 수 있습니다. 여러분의 코딩테스트 준비에 많은 도움이 되기를 바랍니다!

Note: 이 문제는 큰 그래프의 경우 시간 복잡도가 O(V + E)로, 충분히 점검 가능한 시간을 갖고 있습니다. 다양한 테스트 케이스에 대해 여러 번 실험해보는 것을 추천드립니다.

C++ 코딩테스트 강좌, 유클리드 호제법

안녕하세요! 오늘은 코딩테스트에 자주 출제되는 알고리즘 중 하나인 유클리드 호제법에 대해 살펴보려고 합니다. 이 글에서는 유클리드 호제법이 무엇인지 개념적으로 이해하고, 실제 문제를 통해 이를 해결하는 과정을 단계별로 살펴볼 것입니다.

유클리드 호제법이란?

유클리드 호제법(Euclidean algorithm)은 두 수의 최대공약수(GCD, Greatest Common Divisor)를 구하는 효율적인 알고리즘입니다. 고대 그리스의 수학자 유클리드가 처음 소개한 이 방법은 두 수의 유한한 연산으로 GCD를 구할 수 있다는 점에서 매우 유용합니다.

유클리드 호제법의 기본 아이디어는 다음과 같습니다:

  • 두 수 a와 b가 있을 때, a를 b로 나눈 나머지를 r이라고 하자. 즉, a = bq + r (q는 몫, r은 나머지).
  • 이제 GCD(a, b)는 GCD(b, r)과 같습니다. 즉, 나머지로 계속해서 GCD를 구할 수 있습니다.
  • 이 과정을 반복하다 보면 r이 0이 될 때가 오고, 이때의 b가 GCD입니다.

문제 예제: 최대공약수 구하기

이제 유클리드 호제법을 사용하여 최대공약수를 구하는 문제를 해결해 보겠습니다.

문제 설명

두 자연수 A와 B가 주어질 때, 이 두 수의 최대공약수를 구하는 프로그램을 작성하시오.

입력

  • 첫째 줄에 두 자연수 A와 B가 주어진다. (1 ≤ A, B ≤ 1,000,000)

출력

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

예제

입력:

24 36

출력:

12

유클리드 호제법 구현

이제 C++로 위의 문제를 해결하기 위해 유클리드 호제법을 구현해 보겠습니다. 아래는 C++ 코드입니다.


#include <iostream>

using namespace std;

// 최대공약수 함수
int gcd(int a, int b) {
    while (b != 0) {
        int r = a % b; // 나머지 계산
        a = b; // a에 b 대입
        b = r; // b에 나머지 대입
    }
    return a; // 최대공약수 반환
}

int main() {
    int A, B;
    cout << "A와 B를 입력하세요: ";
    cin >> A >> B; // A와 B 입력 받기
    cout << "최대공약수는: " << gcd(A, B) << endl; // 최대공약수 출력
    return 0;
}
        

코드 설명

위 코드의 구조는 다음과 같습니다:

  • #include <iostream>: C++에서 기본 입출력 기능을 사용하기 위한 헤더 파일입니다.
  • using namespace std;: 표준 네임스페이스를 사용하기 위한 구문으로, std::를 생략할 수 있게 해줍니다.
  • int gcd(int a, int b): 두 수 a와 b의 최대공약수를 구하는 함수입니다. 반복문을 통해 b가 0이 될 때까지 r(나머지)을 계산합니다.
  • int main(): 프로그램의 시작점이며, 사용자로부터 입력을 받아 최대공약수를 출력하는 역할을 합니다.

프로그램 실행 과정

프로그램을 컴파일하고 실행하면, 사용자로부터 두 자연수를 입력받습니다. 예를 들어, 24와 36을 입력한다면, 프로그램은 다음과 같이 진행됩니다:

  1. 처음 a = 24, b = 36 이므로 r = 24 % 36 = 24입니다.
  2. 이제 a = 36, b = 24로 바뀝니다. 다시 r = 36 % 24 = 12입니다.
  3. 다시 a = 24, b = 12로 바뀌며 r = 24 % 12 = 0입니다.
  4. b가 0이 되었으므로 a의 값인 12가 최대공약수로 출력됩니다.

유클리드 호제법의 시간 복잡도

유클리드 호제법의 시간 복잡도는 O(log(min(a, b)))입니다. 이는 두 수의 크기가 비슷할 때, 적어도 매번 한 수가 반으로 줄어들기 때문에 매우 효율적이란 장점이 있습니다.

변형 문제

이제 유클리드 호제법을 변형하여 다른 문제를 해결해 볼 수 있습니다. 예를 들어, 두 수의 최소공배수(LCM)를 구하는 문제를 생각해 보겠습니다. 최소공배수는 다음과 같은 공식을 통해 구할 수 있습니다:

LCM(a, b) = (a * b) / GCD(a, b)

이 공식을 C++로 구현하면 다음과 같습니다:


#include <iostream>

using namespace std;

// 최대공약수 함수
int gcd(int a, int b) {
    while (b != 0) {
        int r = a % b;
        a = b;
        b = r;
    }
    return a;
}

// 최소공배수 함수
int lcm(int a, int b) {
    return (a * b) / gcd(a, b); // LCM 계산
}

int main() {
    int A, B;
    cout << "A와 B를 입력하세요: ";
    cin >> A >> B; // A와 B 입력 받기
    cout << "최소공배수는: " << lcm(A, B) << endl; // LCM 출력
    return 0;
}
        

결론

이번 글에서는 유클리드 호제법을 통해 최대공약수 및 최소공배수를 구하는 방법에 대해 알아보았습니다. 이 알고리즘은 효율적이고 간단한 방법으로, 다양한 문제 해결에 활용할 수 있습니다. 코딩 테스트에서 종종 출제되므로, 이를 연습하고 익혀두는 것이 중요합니다. 향후 더 많은 문제와 알고리즘을 다룰 예정이니 많은 관심 부탁드립니다! 감사합니다.