C++ 코딩테스트 강좌, 절댓값 힙 구현하기

이번 강좌에서는 절댓값 힙을 구현하는 알고리즘 문제를 다루고자 합니다. 이 문제는 C++ 프로그래밍에서 자주 출제되는 유형 중 하나로, 우선순위 큐(Priority Queue)를 사용하여 해결할 수 있습니다. 절댓값 힙은 특정 숫자의 절댓값을 기준으로 정렬하는 자료구조입니다.

문제 설명

절댓값 힙은 다음과 같은 연산을 지원합니다:

  • 정수 x를 힙에 삽입합니다.
  • 가장 작은 절댓값을 가진 정수를 힙에서 제거하고 반환합니다. 만약 절댓값이 같은 수가 여러 개일 경우, 더 작은 값을 반환합니다. 만약 힙이 비어있다면 0을 반환합니다.

입력

첫 번째 줄에 연산의 개수 N(1 ≤ N ≤ 100,000)이 주어집니다. 다음 N개의 줄에는 실행할 연산이 주어집니다. 연산은 다음 두 가지로 나뉩니다:

  • 삽입: 0 x (−109x ≤ 109) 형태로 주어지며, 절댓값 힙에 x를 삽입합니다.
  • 삭제: 0 형태로 주어지며, 절댓값 힙에서 가장 작은 절댓값을 가진 정수를 제거하고 반환합니다.

출력

삭제 연산의 결과를 한 줄에 하나씩 출력합니다.

예제 입력

10
1
-1
0
1
0
-1
0
0
0
0

예제 출력

-1
1
0
0
0

문제 접근

이 문제를 해결하기 위해 우선순위 큐를 사용할 것입니다. C++ 표준 라이브러리에서는 std::priority_queue를 제공하는데, 기본적으로 최대 힙으로 작동합니다. 하지만 우리는 절댓값을 기준으로 최소 힙을 만들어야 합니다. 따라서 std::priority_queue를 커스터마이징하여 이를 구현할 것입니다.

힙 구현

절댓값 힙을 구현하기 위해 다음과 같은 비교 함수를 정의할 것입니다:

struct Compare {
    bool operator()(int a, int b) {
        if (abs(a) == abs(b)) {
            return a > b; // 절댓값이 같으면 더 작은 값을 선택
        }
        return abs(a) > abs(b); // 절댓값 기준으로 선택
    }
};

위와 같은 비교 함수를 기준으로 우선순위 큐를 정의하고, 삽입 연산 및 삭제 연산을 수행할 수 있습니다.

C++ 코드 구현

#include 
#include 
#include 
#include 

using namespace std;

// 비교 연산자 정의
struct Compare {
    bool operator()(int a, int b) {
        if (abs(a) == abs(b)) {
            return a > b; // 절댓값이 같으면 더 작은 값을 선택
        }
        return abs(a) > abs(b); // 절댓값을 기준으로 선택
    }
};

int main() {
    int N;
    cin >> N;

    priority_queue, Compare> pq; // 커스터마이징된 우선순위 큐
    
    for (int i = 0; i < N; i++) {
        int x;
        cin >> x;
        if (x == 0) {
            // 삭제 연산
            if (pq.empty()) {
                cout << 0 << endl; // 비어있을 경우 0 출력
            } else {
                cout << pq.top() << endl; // 절댓값 힙에서 가장 작은 값을 출력
                pq.pop(); // 힙에서 삭제
            }
        } else {
            // 삽입 연산
            pq.push(x); // 힙에 값 추가
        }
    }

    return 0;
}

코드 설명

위 C++ 코드는 절댓값 힙을 구현합니다:

  • priority_queue에서 Compare 구조체를 사용하여 우선순위를 설정하고 있습니다.
  • main 함수 내에서 입력을 읽고, 0 입력 시에는 힙에서 데이터를 삭제하고 출력하며, 다른 숫자는 힙에 삽입합니다.

시간 복잡도 분석

이 알고리즘의 시간 복잡도는 다음과 같습니다:

  • 삽입 연산: O(log N)
  • 삭제 연산: O(log N)

따라서 총 N개의 연산을 수행할 경우, 전체 시간 복잡도는 O(N log N)입니다.

결론

이번 강좌에서는 C++로 절댓값 힙을 구현하는 방법을 배우고, 우선순위 큐를 활용하여 효율적으로 데이터를 관리하는 방법을 알아보았습니다. 이 문제는 코딩 테스트에서 중요한 개념 중 하나이므로, 연습을 통해 이해를 깊이 있는 것으로 발전시키기를 바랍니다.

C++ 코딩테스트 강좌, 정수를 1로 만들기

안녕하세요, 여러분. 이번 강좌에서는 C++로 구현할 수 있는 알고리즘 문제 중 하나인 “정수를 1로 만들기” 문제를 다루어 보겠습니다. 이 문제는 취업 준비를 위한 코딩 테스트에서 자주 출제되는 유형 중 하나로, 다양한 사고방식을 요구합니다. 따라서 이 문제를 풀면서 기본적인 알고리즘 설계 및 트리밍 기술을 향상시키고, C++ 프로그래밍 실력을 키울 수 있습니다.

문제 설명

정수 x가 주어질 때, 다음 두 가지 연산을 이용하여 정수를 1로 만들려고 합니다:

  • 짝수 x가 주어지면 x를 2로 나눈다.
  • 홀수 x가 주어지면 x에서 1을 뺀다.

연산은 1회만 수행 가능하며, 상기 연산을 반복하여 x를 1로 만드는 최소 횟수를 구하는 문제입니다. 단, x의 범위는 1 이상 106 이하입니다.

입력


x = 10

출력


result = 5

문제 접근 방법

이 문제를 해결하기 위해서는 재귀적인 접근 방법 또는 반복문을 통해 x를 1로 만들어가는 과정에서 최소 횟수를 계산해야 합니다. 알고리즘을 구현하기 위해 다음 단계들을 고려해볼 수 있습니다.

1. 기본 알고리즘 설계

처음에는 x가 홀수인지 짝수인지를 판단하고 해당 조건에 따라 적절한 연산을 적용합니다. 이 과정을 반복하면서 x가 1이 될 때까지의 카운트를 증가시킵니다. 재귀적으로 구현할 수도 있지만, for문을 사용하는 것이 더 간단할 수 있습니다.

2. 시간 복잡도 분석

이 알고리즘의 시간 복잡도는 x의 값을 따릅니다. x가 최대 10^6이므로, 최대 20회 이하로 연산이 수행될 것입니다. 이는 매우 효율적인 방법입니다.

C++ 코드 구현

이제 위의 알고리즘을 C++로 구현해보겠습니다. 아래 코드는 주어진 정수를 1로 만드는 데 필요한 최소 연산 횟수를 계산합니다.

#include 
using namespace std;

int makeOne(int x) {
    int count = 0; // 연산 횟수를 세기 위한 변수
    while (x > 1) { // x가 1보다 클 경우 계속 반복
        if (x % 2 == 0) { // x가 짝수일 경우
            x /= 2; // x를 2로 나눈다
        } else { // x가 홀수일 경우
            x -= 1; // x에서 1을 뺀다
        }
        count++; // 연산 횟수 증가
    }
    return count; // 최종 연산 횟수 반환
}

int main() {
    int x;
    cout << "정수를 입력하세요: ";
    cin >> x; // 사용자로부터 정수 입력 받기
    int result = makeOne(x); // makeOne 함수 호출
    cout << "정수를 1로 만드는 최소 연산 횟수: " << result << endl; // 결과 출력
    return 0;
}

코드 설명

위의 코드는 주어진 정수 x를 인자로 받아 해당 정수를 1로 만드는 최소 연산 횟수를 구하는 makeOne 함수를 정의하고 있습니다. while 반복문을 통해 x가 1보다 클 경우 연산을 계속 수행하며, 매 반복마다 연산 횟수를 기록합니다. 이를 통해 최종적으로 1로 만들기 위한 총 연산 횟수를 반환합니다.

테스트 및 검증

이제 여러 테스트 케이스로 코드가 제대로 작동하는지 확인해보겠습니다.


정수를 입력하세요: 10
정수를 1로 만드는 최소 연산 횟수: 5

정수를 입력하세요: 15
정수를 1로 만드는 최소 연산 횟수: 8

정수를 입력하세요: 1
정수를 1로 만드는 최소 연산 횟수: 0

결론

이번 강좌에서는 “정수를 1로 만들기” 알고리즘 문제를 해결해보았습니다. 이 문제를 통해 C++의 기초적인 문법과 알고리즘 설계, 그리고 코드 구현 방법을 다루었습니다. 알고리즘 문제를 해결하는 과정에서 마주치는 다양한 상황들과 이를 극복하기 위한 방법들을 항상 고민하는 것이 중요합니다. 문제 해결 능력을 키우기 위해 다양한 문제를 풀어보시기를 권장합니다. 다음 강좌에서도 유익한 내용을 가지고 돌아오겠습니다. 감사합니다!

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

안녕하세요! 이번 강좌에서는 이항계수(Binomial Coefficient)의 개념을 심화하여 다룰 것입니다. 이항계수는 조합론의 중요한 개념으로, 주어진 n 개의 원소 중에서 k 개를 선택하는 방법의 수를 나타냅니다. 이 강좌에서 다룰 문제는 “nCr” 또는 “이항계수” 문제로, 효율적인 구현 방법에 대해 알아보겠습니다.

문제 설명

문제는 다음과 같습니다:

주어진 정수 n과 k에 대해 이항계수 C(n, k)를 출력하시오. (단, 0 ≤ k ≤ n ≤ 1000)

이항계수 정의

이항계수 C(n, k)는 다음과 같이 정의됩니다:

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

여기서 n!은 n factorial로, n × (n-1) × … × 1의 곱을 의미합니다. 이 정의에 따르면, 이항계수를 계산하기 위해서는 팩토리얼을 계산해야 합니다. 그러나 n이 1000일 경우, n!은 매우 큰 수가 되기 때문에 직접 계산하기보다는 효율적인 방법이 필요합니다.

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

이항계수를 계산하는 보다 효율적인 방법 중 하나는 동적 프로그래밍을 사용하는 것입니다. 이 과정을 통해 이항계수를 효율적으로 계산하는 방법을 살펴보겠습니다.

점화식

C(n, k)는 다음의 점화식으로도 표현됩니다:

C(n, k) = C(n-1, k-1) + C(n-1, k) (단, k > 0)
C(n, 0) = 1 (k가 0일 경우)
C(n, n) = 1 (k가 n일 경우)

이 점화식을 바탕으로 재귀적으로 이항계수를 계산할 수 있지만, 이렇게 하면 중복 계산이 발생하므로 비효율적입니다. 따라서 메모이제이션 또는 동적 프로그래밍 기법을 사용하여 계산할 것입니다.

코드 구현

다음은 C++ 언어로 이항계수를 계산하는 동적 프로그래밍 코드입니다:


#include <iostream>
#include <vector>

using namespace std;

long long binomialCoefficient(int n, int k) {
    vector<vector<long long>> C(n + 1, vector<long long>(k + 1, 0));
  
    // C(n, 0) = 1
    for (int i = 0; i <= n; i++) {
        C[i][0] = 1;
    }
  
    // C(n, k) = C(n - 1, k - 1) + C(n - 1, k)
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= min(i, k); j++) {
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
  
    return C[n][k];
}

int main() {
    int n, k;
    cout << "Enter n and k: ";
    cin << n >> k;
    cout << "C(" << n << ", " << k << ") = " << binomialCoefficient(n, k) << endl;
    return 0;
}

코드 설명

1. vector<vector<long long>> C(n + 1, vector<long long>(k + 1, 0));: 이항계수를 저장할 2차원 벡터를 선언하고 초기화합니다.

2. C[i][0] = 1;: n이 0일 때, 이항계수는 항상 1이라는 점을 활용하여 첫 번째 열을 초기화합니다.

3. 규칙에 따라 각 값을 계산하며 2차원 벡터 C에 채웁니다.

4. 마지막으로 C[n][k]를 반환하여 결과를 출력합니다.

예제 실행

이제 위의 코드를 실행해 보겠습니다:

Enter n and k: 5 2
C(5, 2) = 10

위의 예제에서 n = 5, k = 2일 때, C(5, 2)의 값은 10으로 올바르게 계산되었습니다.

시간 복잡도 분석

위의 코드에서 이항계수를 동적 프로그래밍을 통해 구하기 때문에 시간복잡도는 O(n * k)입니다. 최악의 경우에 n = 1000일 때, 이 복잡도는 할 수 있는 범위 내입니다. 공간 복잡도는 O(n * k)로, C 벡터를 저장하는 데 필요한 공간을 나타냅니다.

결론

이번 강좌에서는 이항계수를 동적 프로그래밍을 통해 효율적으로 계산했습니다. 이항계수는 조합론, 확률론 등 다양한 분야에서 중요한 개념이므로 이해하고 연습하는 것이 중요합니다. 주어진 문제를 동적으로 푸는 능력을 키우기 위해 다양한 테스트 케이스로 연습해 보세요.

이 강좌가 도움이 되셨길 바라며, 다음 강좌에서 또 만나요!

C++ 코딩테스트 강좌, 임계 경로 구하기

안녕하세요. 이번 강좌에서는 C++ 코딩테스트에서 자주 출제되는 알고리즘 문제 중 하나인 “임계 경로 구하기”에 대해 다루어 보겠습니다. 이 문제는 주로 그래프 이론과 관련된 중요한 개념을 이해하는 데 도움이 됩니다.

문제 정의

주어진 방향 그래프에서 각 정점이 특정 작업을 수행하는 시간을 가지고 있습니다. 임계 경로는 모든 정점을 방문하고, 시작 정점에서 도착 정점으로 가는 경로 중에서 가장 긴 경로의 총 작업 시간을 의미합니다.

문제 설명

그래프의 정점 수 n과 간선 수 m이 주어지고, 각 정점 i에 대해 작업 시간이 t[i]로 주어집니다. 다음과 같은 조건을 만족하는 경로의 작업 시간의 최대값을 구하는 문제입니다.

입력:
n, m
t[0], t[1], ..., t[n-1]
간선: (u1, v1), (u2, v2), ..., (um, vm)
출력:
최대 작업 시간

입력 예시

5 6
2 3 1 5 4
0 1
0 2
1 3
2 3
2 4
3 4

출력 예시

10

문제 해결 전략

1. 그래프 표현

우리는 먼저 주어진 정보에 따라 그래프를 인접 리스트 형태로 구성합니다. 이를 통해 각 정점에서 연결된 정점으로의 이동을 쉽게 관리할 수 있습니다.

2. 최장 경로 찾기

DFS(Depth-First Search) 또는 DAG(Directed Acyclic Graph) 성질을 이용해 최장 경로를 구할 수 있습니다. 각 정점을 방문하면서 최장 경로의 최대 작업 시간을 저장해 나가는 방식으로 진행합니다.

3. 동적 프로그래밍(DP) 활용

특히, 임계 경로 구하기 문제는 동적 프로그래밍을 통해 효율적으로 해결할 수 있습니다. 각 정점을 기준으로 도달 가능한 모든 경로의 최대를 누적하여 기록합니다.

C++ 코드 구현

#include 
#include 
#include 
using namespace std;

vector timeTaken; // 각 정점의 작업 시간
vector> graph; // 그래프 인접 리스트
vector dp; // DP 배열

int dfs(int node) {
    if (dp[node] != -1) return dp[node];
    int maxTime = 0;
    for (int next : graph[node]) {
        maxTime = max(maxTime, dfs(next));
    }
    dp[node] = maxTime + timeTaken[node];
    return dp[node];
}

int main() {
    int n, m;
    cin >> n >> m;
    
    timeTaken.resize(n);
    graph.resize(n);
    dp.assign(n, -1);
    
    for (int i = 0; i < n; i++) {
        cin >> timeTaken[i];
    }
    
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        graph[u].push_back(v);
    }

    int result = 0;
    for (int i = 0; i < n; i++) {
        result = max(result, dfs(i));
    }

    cout << result << endl;
    return 0;
}

코드 설명

위 코드는 그래프를 DFS 형태로 탐색하여 임계 경로의 최대 작업 시간을 계산하는 방식입니다. 주요 요소는 다음과 같습니다:

  • 그래프 생성: 입력받은 간선을 통해 인접 리스트 형태로 그래프를 구성합니다.
  • DFS 함수: 각 정점을 시작으로 연결된 정점들을 재귀적으로 탐색하며 최장 경로의 시간을 계산합니다.
  • DP 배열: 동일한 정점을 여러 번 방문하지 않도록 DP 배열을 활용하여 메모이제이션 기법을 사용합니다.

시간 복잡도

이 알고리즘의 시간 복잡도는 O(n + m)입니다. n은 정점의 수, m은 간선의 수입니다. 최악의 경우 모든 정점을 한 번씩 방문해야 하므로, 이 정도의 복잡도는 일반적으로 효율적입니다.

결론

이번 강좌에서는 C++를 사용하여 임계 경로 구하기 문제를 해결하는 방법에 대해 학습하였습니다. 그래프 이론, 동적 프로그래밍 및 DFS의 개념을 통해 이러한 유형의 문제를 해결할 수 있습니다. 앞으로도 다양한 알고리즘 문제를 연습하여 더 나은 프로그래밍 실력을 갖추시길 바랍니다.

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

안녕하세요! 이번 포스팅에서는 C++ 코딩 테스트에서 자주 등장하는 알고리즘 문제인 “이항계수 구하기”에 대해 다뤄보겠습니다. 이 문제는 조합(combination)과 관련이 깊으며, 수학적 기초와 코딩 능력을 동시에 요구하는 유형입니다. 또한, 다양한 해법과 그 알고리즘에 대한 이해는 취업 시험을 준비하는 데 큰 도움이 될 것입니다.

문제 정의

우리가 구할 이항계수는 주어진 정수 n과 k에 대해 다음 수식을 통해 정의됩니다:

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

여기서 n!은 n의 팩토리얼을 의미합니다. 문제는 n과 k가 주어졌을 때 이항계수 C(n, k)를 계산하는 것입니다. 예를 들어, n=5이고 k=2라면, C(5, 2)는 10입니다.

문제의 입력과 출력

문제의 입력은 두 개의 정수 n과 k이고, 출력은 C(n, k)의 값입니다.

입력 형식

  • 첫 줄: 두 정수 n(0 ≤ n ≤ 30)과 k(0 ≤ k ≤ n)

출력 형식

  • 한 줄에 C(n, k)의 값 출력

문제 풀이 과정

이제 문제를 풀기 위한 접근 방법을 단계별로 살펴보겠습니다. 우리는 기본적으로 세 가지 접근 방법을 사용할 수 있습니다.

1. 재귀적 접근 (Recursive Approach)

이항계수는 재귀적으로 정의될 수 있습니다. 즉, 다음의 점화식을 사용할 수 있습니다:

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

기저 사례로는 C(n, 0) = 1 (모든 n에 대해)와 C(n, n) = 1 (모든 n에 대해)입니다. 파라미터 n과 k가 주어질 때, 이 점화식을 사용하여 재귀적으로 이항계수를 계산할 수 있습니다. 하지만 이 접근은 중복 계산이 많아서 효율적이지 않습니다.

재귀적 구현 예시


#include 
using namespace std;

// Recursive function to find the binomial coefficient C(n, k)
int binomialCoefficient(int n, int k) {
    // Base cases
    if (k == 0 || k == n) return 1;
    // Recursive call
    return binomialCoefficient(n - 1, k - 1) + binomialCoefficient(n - 1, k);
}

int main() {
    int n, k;
    cout << "Enter n and k: ";
    cin >> n >> k;
    cout << "C(" << n << ", " << k << ") = " << binomialCoefficient(n, k) << endl;
    return 0;
}

2. 동적 프로그래밍 (Dynamic Programming)

재귀적 접근은 시간 복잡도가 O(2^n)으로 비효율적입니다. 이를 개선하기 위해 동적 프로그래밍을 사용할 수 있습니다. 이 방식은 중복 계산을 피하고, 메모이제이션 또는 타뷸레이션 기법을 통해 이미 계산한 값을 저장하여 효율성을 높입니다.

동적 프로그래밍 구현 예시


#include <iostream>
using namespace std;

// Function to calculate binomial coefficient using DP
int binomialCoefficientDP(int n, int k) {
    int C[n + 1][k + 1];

    // Calculate the binomial coefficient using a bottom-up approach
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= min(i, k); j++) {
            if (j == 0 || j == i)
                C[i][j] = 1;
            else
                C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
    return C[n][k];
}

int main() {
    int n, k;
    cout << "Enter n and k: ";
    cin >> n >> k;
    cout << "C(" << n << ", " << k << ") = " << binomialCoefficientDP(n, k) << endl;
    return 0;
}

3. 팩토리얼을 이용한 계산 (Factorial Method)

마지막 방법으로는 팩토리얼을 직접 계산하여 이항계를 구하는 방법입니다. 이 방식은 수학적 공식을 직접 구현하여 C(n, k)를 계산하는 것입니다.

팩토리얼을 이용한 구현 예시


#include <iostream>
using namespace std;

// Function to return the factorial of a number
int factorial(int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

// Function to calculate the binomial coefficient
int binomialCoefficientFactorial(int n, int k) {
    return factorial(n) / (factorial(k) * factorial(n - k));
}

int main() {
    int n, k;
    cout << "Enter n and k: ";
    cin >> n >> k;
    cout << "C(" << n << ", " << k << ") = " << binomialCoefficientFactorial(n, k) << endl;
    return 0;
}

비교와 최적화

세 가지 방법의 시간 복잡도는 다음과 같습니다:

  • 재귀적 접근: O(2^n)
  • 동적 프로그래밍: O(n * k)
  • 팩토리얼 계산: O(n)

따라서, n과 k의 크기가 작을 경우 팩토리얼 사용이 간편하고 빠르지만, n과 k가 커질 수록 동적 프로그래밍 방식이 더 효율적입니다. 팩토리얼 방식은 n이 큰 경우 오버플로우가 발생할 수 있는 점을 유념해야 합니다.

결론

이번 포스팅에서는 이항계수 구하기 문제에 대해 알아보았습니다. 다양한 방법을 통해 이 문제를 해결할 수 있으며, 각각의 방법의 장단점을 고려하여 적절한 방식을 선택하는 것이 중요합니다. 알고리즘의 이해는 코딩 테스트뿐만 아니라 실제 개발에서도 큰 도움이 됩니다.

다음 포스팅에서는 이항계수를 활용한 더 복잡한 문제를 다룰 예정이니 기대해 주세요!

요약하자면, 이항계수 문제는 수학과 알고리즘의 접목이 좋은 사례로, 취업 준비 뿐만 아니라 알고리즘 공부에도 여전히 중요한 주제입니다. 여러분도 다양한 문제를 풀어보며 경험치를 쌓아 가시길 바랍니다.

읽어 주셔서 감사합니다!