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;
}
        

결론

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

C++ 코딩테스트 강좌, 위상 정렬

안녕하세요! 이번 글에서는 위상 정렬에 대해 알아보겠습니다. 위상 정렬은 방향성 비순환 그래프(DAG, Directed Acyclic Graph)에서 정점을 선형으로 나열하는 알고리즘으로, 특정한 순서를 유지해야 할 때 유용합니다. 예를 들어 작업의 종속성을 처리하거나 컴파일러가 코드의 실행 순서를 결정할 때 사용됩니다.

문제 설명

다음 문제를 해결해 보겠습니다.

문제: 작업 스케줄링

여러 개의 작업이 주어졌을 때, 각 작업은 특정한 선행 작업이 필요합니다. 선행 작업이 모두 완료된 후에만 해당 작업을 수행할 수 있습니다. 그래프의 정점은 작업을 나타내고, 간선은 선행 작업을 나타냅니다. 주어진 작업을 가능한 순서로 나열하시오.

입력

첫 번째 줄에는 정점의 개수 N (1 ≤ N ≤ 1000)과 간선의 개수 M (0 ≤ M ≤ 10000)이 주어진다.

다음 M개의 줄에는 x y 쌍이 주어지며, 이는 작업 x가 작업 y보다 먼저 수행되어야 함을 의미한다.

출력

작업을 수행할 수 있는 순서를 한 줄에 공백으로 구분하여 출력한다. 만약 순서가 불가능하다면 -1을 출력한다.

예제 입력

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

예제 출력

1 2 3 4 5 6

위상 정렬의 원리

위상 정렬은 두 가지 방법으로 구현할 수 있습니다: DFS(깊이 우선 탐색)BFS(너비 우선 탐색). 여기서는 BFS를 이용한 Kahn의 알고리즘을 소개합니다. Kahn의 알고리즘은 다음과 같은 절차로 진행됩니다.

  1. 모든 정점의 진입 차수를 기록합니다.
  2. 진입 차수가 0인 정점을 큐에 넣습니다.
  3. 큐에서 정점을 하나씩 꺼내고, 그 정점을 결과 리스트에 추가합니다.
  4. 꺼낸 정점으로부터 나가는 모든 간선을 제거하고, 각 간선의 도착 정점의 진입 차수를 업데이트합니다. 진입 차수가 0이 되는 정점을 큐에 추가합니다.
  5. 큐가 빌 때까지 반복합니다. 결과 리스트의 크기가 입력한 정점의 개수와 다르면 사이클이 존재하는 것이므로 -1을 반환합니다.

기본 코드 구현

아래는 C++로 위상 정렬을 구현한 기본적인 코드입니다.


#include <iostream>
#include <vector>
#include <queue>
using namespace std;

void topologicalSort(int n, vector<vector<int>> &graph) {
    vector<int> inDegree(n + 1, 0);
    vector<int> result;

    // 진입 차수 계산
    for (int i = 1; i <= n; i++) {
        for (int j : graph[i]) {
            inDegree[j]++;
        }
    }

    queue<int> q;

    // 진입 차수가 0인 정점 큐에 삽입
    for (int i = 1; i <= n; i++) {
        if (inDegree[i] == 0) {
            q.push(i);
        }
    }

    while (!q.empty()) {
        int u = q.front();
        q.pop();
        result.push_back(u);

        for (int v : graph[u]) {
            inDegree[v]--;
            if (inDegree[v] == 0) {
                q.push(v);
            }
        }
    }

    // 사이클 확인
    if (result.size() != n) {
        cout << -1 << endl;
    } else {
        for (int i = 0; i < result.size(); i++) {
            cout << result[i] << ' ';
        }
        cout << endl;
    }
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> graph(n + 1);

    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        graph[x].push_back(y);
    }

    topologicalSort(n, graph);
    return 0;
}
    

코드 설명

위 코드에서는 먼저 진입 차수를 담기 위해 inDegree라는 배열을 선언합니다. 각 정점의 진입 차수를 계산한 뒤, 진입 차수가 0인 정점을 큐에 넣습니다. 그 다음 큐에서 정점을 꺼내고, 꺼낸 정점으로부터 나가는 간선을 모두 제거하고 진입 차수를 업데이트합니다. 만약 result 리스트의 크기가 정점의 개수와 다르면 사이클이 존재하므로 -1을 출력합니다.

복잡도 분석

위상 정렬의 시간 복잡도는 O(N + M)입니다. 여기서 N은 정점의 개수, M은 간선의 개수입니다. 진입 차수를 세는데 O(M) 시간이 소요되고, 각 정점마다 진입 차수를 업데이트하는 과정에서도 O(M) 시간이 걸립니다. 따라서 전체적으로 O(N + M)의 시간 복잡도를 가집니다. 공간 복잡도는 O(N + M)으로, 그래프를 저장하기 위한 리스트와 진입 차수 배열을 고려해야 합니다.

추가 연습 문제

아래의 추가 연습 문제를 통해 위상 정렬을 더 깊이 이해하고 적용해보세요.

  1. 주어진 작업을 기반으로 다양한 작업 조합을 만들고 위상 정렬을 적용해 보세요.
  2. 사이클이 포함된 그래프를 입력으로 주어졌을 때, 적절한 에러 처리를 구현해 보세요.
  3. 진입 차수가 같은 정점이 여러 개일 경우, 그 중 하나의 정점을 무작위로 선택하도록 구현해 보세요.

결론

이번 포스트에서는 위상 정렬의 개념과 구현 방법, 예제 문제를 통해 위상 정렬의 활용 방법에 대해 배워보았습니다. 위상 정렬은 다양한 분야에서 많이 응용되는 알고리즘이므로 충분히 이해하고 연습하는 것이 중요합니다.

이제 여러분도 위상 정렬을 활용하여 복잡한 작업을 정렬하고 관리하는 데에 자신감을 가질 수 있기를 바랍니다. 계속해서 연습하고 다양한 문제를 풀어보세요!

C++ 코딩테스트 강좌, 원하는 정수 찾기

안녕하세요! 여러분과 함께하는 C++ 코딩 테스트 강좌입니다. 오늘의 주제는 ‘원하는 정수 찾기’입니다. 이 강좌는 C++로 코딩 테스트를 준비하는 여러분에게 도움이 될 것입니다. 문제를 이해하고 올바른 해결 방법을 찾는 과정을 자세히 설명하겠습니다. 본 강좌는 총 20,000자 이상으로 설계되어 있습니다.


문제 설명

주어진 정수 배열 arr와 하나의 정수 target이 있습니다. arr에서 target을 찾아 그 인덱스를 반환하는 프로그램을 작성하세요. arrtarget이 존재하지 않을 경우 -1을 반환합니다. 배열의 길이는 최대 10^6입니다. 배열은 정렬되어 있지 않을 수도 있습니다.

입력

arr = [2, 3, 4, 7, 11, 15, 20]
target = 7

출력

결과: 3

문제 분석

이 문제는 배열에서 특정 값을 찾는 문제로, 다양한 방식으로 해결할 수 있습니다. 일반적인 방식으로는 선형 탐색과 이진 탐색이 있습니다. 하지만 배열이 정렬되어 있는 경우에는 이진 탐색이 더 효율적입니다. 하지만 주어진 문제에서는 배열이 정렬되어 있지 않아 선형 탐색을 사용해 문제를 해결하겠습니다.

알고리즘 선택

문제에서 정렬되지 않은 배열을 주기 때문에 선형 탐색(linear search)을 사용할 것입니다. 선형 탐색은 배열의 각 요소를 하나씩 확인하여 찾고자 하는 값을 찾는 단순한 알고리즘입니다. 시간 복잡도는 O(n)입니다.

선형 탐색 알고리즘

선형 탐색의 기본 아이디어는 다음과 같습니다:

  1. 배열의 첫 번째 요소부터 시작합니다.
  2. 원하는 정수를 찾을 때까지 배열의 각 요소를 순차적으로 비교합니다.
  3. 찾으면 해당 요소의 인덱스를 반환합니다.
  4. 배열 끝에 도달할 때까지 찾지 못하면 -1을 반환합니다.

코드 구현

이제 C++로 선형 탐색 알고리즘을 구현해 보겠습니다. 아래에 코드를 작성하였습니다:

#include <iostream>
#include <vector>

int findTarget(const std::vector<int> &arr, int target) {
    for (size_t i = 0; i < arr.size(); ++i) {
        if (arr[i] == target) {
            return i; // Target found, return index
        }
    }
    return -1; // Target not found
}

int main() {
    std::vector<int> arr = {2, 3, 4, 7, 11, 15, 20};
    int target = 7;

    int index = findTarget(arr, target);
    if (index != -1) {
        std::cout << "결과: " << index << std::endl;
    } else {
        std::cout << "결과: -1" << std::endl;
    }

    return 0;
}

코드 설명

위의 코드에서 findTarget 함수는 배열과 찾고자 하는 값을 입력으로 받아서 해당 값을 배열에서 찾아 반환합니다.

  1. for 루프를 통해 배열의 각 요소에 접근합니다.
  2. 현재 요소가 target과 같은지 확인합니다. 같다면 해당 인덱스를 반환합니다.
  3. 모든 요소를 확인한 후에도 찾지 못했다면 -1을 반환하여 ‘찾지 못했다’는 의미를 명시합니다.

성능 분석

위의 선형 탐색 알고리즘의 시간 복잡도는 O(n)입니다. 최악의 경우에는 배열의 모든 요소를 확인해야 하므로, n이 배열의 크기일 때 이런 성능을 보입니다. 메모리 복잡도는 O(1)입니다.

테스트

이제 몇 가지 테스트 케이스를 만들어 알고리즘의 유효성을 검증해 보겠습니다.

테스트 케이스

  1. arr = [1, 2, 3, 4, 5], target = 3 -> 결과: 2
  2. arr = [10, 20, 30, 40, 50], target = 25 -> 결과: -1
  3. arr = [5, 1, 9, 3], target = 1 -> 결과: 1

결론

오늘은 C++을 사용하여 원하는 정수를 찾는 문제를 해결했습니다. 우리는 선형 탐색 알고리즘을 사용하여 배열에서 특정 숫자를 찾는 방법을 배웠습니다. 이런 문제는 실제 인터뷰에서도 자주 출제되므로, 충분한 연습을 통해 능숙해져야 합니다. 다음 강좌에서는 다른 알고리즘과 더 복잡한 문제를 다룰 예정이니 많은 기대 바랍니다!

참고자료

자세한 내용은 다음 자료를 참조하시기 바랍니다:

C++ 코딩테스트 강좌, 오큰수 구하기

이번 강좌에서는 C++을 사용하여 “오큰수 구하기”라는 문제를 해결해보겠습니다. 이 문제는 주어진 수열에서 각 원소에 대해 자신보다 크고, 가장 가까운 위치에 있는 수를 찾는 문제입니다. 이를 통해 스택을 활용한 문제 해결 방법과 효율적인 알고리즘 설계를 배워보겠습니다.

문제 설명

주어진 정수 배열에서 각 배열의 원소에 대해, 그 원소보다 큰 수가 오른쪽에 있는 가장 가까운 위치를 찾아 해당 수를 출력합니다. 만약 그런 수가 존재하지 않는다면 -1을 출력해야 합니다.

입력

  • 첫 번째 줄에 배열의 크기 N (1 ≤ N ≤ 1,000,000)
  • 두 번째 줄에 N개의 정수로 이루어진 배열 A (0 ≤ A[i] ≤ 1,000,000)

출력

  • N개의 정수로 이루어진 배열 B를 출력해야 하며, B[i]는 A[i]의 오른쪽에서 가장 가까운 큰 수입니다. 그런 수가 없다면 -1을 출력해야 합니다.

예제 입력


5
2 1 2 4 3

예제 출력


4 2 4 -1 -1

문제 접근 방법

이 문제를 해결하는 가장 효율적인 방법은 스택(Stack) 자료구조를 사용하는 것입니다. 배열의 맨 뒤에서부터 시작하여 각 원소를 순회하면서 다음과 같은 과정을 수행합니다.

  1. 스택이 비어있지 않고, 현재 원소가 스택의 가장 위에 있는 원소보다 크면, 스택에서 원소를 pop 합니다. 이 때 pop한 원소는 현재 원소의 오큰수가 됩니다.
  2. 현재 원소의 인덱스와 오큰수 값을 배열에 저장합니다.
  3. 현재 원소를 스택에 push합니다.

이 과정을 배열의 처음까지 반복하여 모든 원소의 오큰수를 찾을 수 있습니다.

시간 복잡도

이 알고리즘의 시간 복잡도는 O(N)입니다. 각 원소는 스택에 단 한 번 push되고 단 한 번 pop되므로, 모든 원소를 들른다고 하더라도 스택의 원소 개수는 N을 넘지 않습니다.

구현

이제 실제로 위의 알고리즘을 C++로 구현해보겠습니다.


#include <iostream>
#include <vector>
#include <stack>
using namespace std;

void findNextGreaterElement(const vector<int>& arr, vector<int>& result) {
    stack<int> s; // 스택 선언

    for (int i = arr.size() - 1; i >= 0; --i) {
        // 스택에서 현재 원소보다 작거나 같은 원소를 제거
        while (!s.empty() && s.top() <= arr[i]) {
            s.pop();
        }
        // 스택이 비어있으면, 오큰수는 -1
        if (s.empty()) {
            result[i] = -1;
        } else {
            result[i] = s.top(); // 스택의 가장 위 원소가 오큰수
        }
        s.push(arr[i]); // 현재 원소를 스택에 추가
    }
}

int main() {
    int N;
    cout << "배열의 크기를 입력하세요: ";
    cin >> N;

    vector<int> arr(N);
    cout << "배열 원소를 입력하세요: ";
    for (int i = 0; i < N; ++i) {
        cin >> arr[i];
    }

    vector<int> result(N);
    findNextGreaterElement(arr, result);

    cout << "오큰수: ";
    for (int i = 0; i < N; ++i) {
        cout << result[i] << " ";
    }
    cout << endl;

    return 0;
}

코드 설명

위의 코드는 “오큰수 구하기” 문제를 해결하기 위한 C++ 프로그램입니다. 다음은 각 부분에 대한 설명입니다.

  • 헤더 파일 포함: iostream, vector, stack 라이브러리를 포함하여 입출력, 벡터 사용, 스택 사용을 가능하게 합니다.
  • findNextGreaterElement 함수: 이 함수는 입력으로 주어진 배열과 결과를 저장할 벡터를 받아, 오큰수를 찾는 핵심 로직을 포함하고 있습니다.
  • main 함수: 프로그램의 시작점으로, 사용자로부터 배열의 크기와 원소를 입력받고, 오큰수를 계산한 후 결과를 출력합니다.

결론

이번 강좌에서는 “오큰수 구하기” 문제를 C++을 사용하여 효율적으로 해결하는 방법을 배웠습니다. 스택을 활용하여 배열의 각 원소에 대해 오큰수를 찾아내는 과정에서 스택의 특징과 알고리즘의 효율성을 느낄 수 있었습니다. 실제 코딩 테스트에서는 이러한 자료구조와 알고리즘을 활용할 수 있는 능력이 중요합니다.

코딩테스트에서 자주 출제되는 문제인 만큼 다양한 변형 문제에 대해서도 고민해보고, 스택을 활용한 문제 해결 능력을 키우는 것이 중요합니다. 앞으로도 유사한 문제들을 접하면서, 다양한 알고리즘과 자료구조를 익혀보시기 바랍니다.

C++ 코딩테스트 강좌, 외판원의 순회 경로 짜기

작성자: 조광형

작성일: 2024년 11월 26일

문제 정의

“외판원 문제”는 주어진 도시들을 모두 방문하고 다시 시작점으로 돌아오는 최소 경로를 찾는 문제입니다.
이 문제는 컴퓨터 과학 및 최적화 이론에서 중요한 문제이며, 다양한 실제 문제에 응용될 수 있습니다.
외판원 문제는 특히 NP-완전 문제로 알려져 있으며, 효과적인 알고리즘을 통해 해결할 수 있는 방법을 탐구해 보겠습니다.

문제 설명

                주어진 N개의 도시가 있으며, 각 도시 사이의 거리가 주어집니다. 외판원은 
                모든 도시를 한 번씩 방문한 후, 다시 시작 도시로 돌아와야 합니다. 
                최소 비용으로 모든 도시를 방문할 경로를 구하세요.
                
                입력:
                - 첫째 줄: 도시의 개수 N (1 ≤ N ≤ 20)
                - 둘째 줄: N x N 행렬의 형태로 거리 정보. 
                  (행렬의 i행 j열은 도시 i에서 도시 j까지의 거리)
                
                출력:
                - 최소 비용
            

문제 접근 방법

이 문제의 해결을 위해 다이나믹 프로그래밍(Dynamic Programming)과 비트 마스킹(Bit Masking) 기법을 사용합니다.
다이나믹 프로그래밍을 통해 서브 문제를 해결하고, 비트 마스킹을 통해 도시 방문 여부를 관리합니다.
N개의 도시가 있을 때, 각 도시의 방문 상태는 비트로 표현할 수 있습니다.
예를 들어, N=4의 경우 0000은 어떤 도시도 방문하지 않은 상태, 1111은 모든 도시를 방문한 상태를 의미합니다.
이를 통해 각 서브 문제에서 이전에 계산된 값을 활용하여 최소 비용을 계산할 수 있습니다.

알고리즘 설명

1. **비트마스킹 설정**:
각 도시의 상태를 비트로 표현합니다. 예를 들어, 4개의 도시가 있으면 0b0000부터 0b1111까지 표현할 수 있습니다.
이 비트마스크를 사용하여 방문한 도시를 추적할 수 있습니다.

2. **재귀 호출**:
현재 도시와 방문한 도시의 비트마스크를 인자로 받고, 모든 도시를 방문하기 위한 최소 비용을 계산합니다.

3. **메모이제이션**:
이전에 계산한 결과를 저장하여 중복 계산을 줄입니다. 상태는 `(현재 도시, 방문한 도시의 비트마스크)`로 저장합니다.

4. **최종 경로 계산**:
모든 도시를 방문한 경우, 시작 도시로 돌아오는 비용을 더하여 최소 경로를 반환합니다.

C++ 코드 구현

                #include 
                #include 
                #include 
                #include 

                using namespace std;

                int N; // 도시의 개수
                int dist[20][20]; // 거리 행렬
                int dp[1 << 20][20]; // 메모이제이션

                int tsp(int mask, int pos) {
                    // 모든 도시를 방문했으면 시작 도시로 돌아간다
                    if (mask == (1 << N) - 1) {
                        return dist[pos][0]; // 시작 도시로의 거리
                    }

                    // 이미 계산된 결과가 있으면 이를 즉시 반환
                    if (dp[mask][pos] != -1) {
                        return dp[mask][pos];
                    }

                    int ans = INT_MAX; // 초기값은 무한대를 설정
                    for (int city = 0; city < N; city++) {
                        // 도시가 방문되지 않았다면 다음 도시로 이동
                        if ((mask & (1 << city)) == 0) {
                            int newAns = dist[pos][city] + tsp(mask | (1 << city), city);
                            ans = min(ans, newAns);
                        }
                    }

                    return dp[mask][pos] = ans; // 결과 저장
                }

                int main() {
                    // 도시의 개수를 입력받는다
                    cout << "도시의 개수를 입력하세요: ";
                    cin >> N;

                    // 거리 행렬 입력
                    cout << "거리 행렬을 입력하세요:" << endl;
                    for (int i = 0; i < N; i++) {
                        for (int j = 0; j < N; j++) {
                            cin >> dist[i][j];
                        }
                    }

                    // 메모이제이션 배열 초기화
                    memset(dp, -1, sizeof(dp));

                    // 최소 비용 계산 및 출력
                    int result = tsp(1, 0); // 시작할 때 첫 번째 도시를 방문
                    cout << "최소 비용: " << result << endl;

                    return 0;
                }
            

결론

외판원 문제는 알고리즘 및 데이터 구조의 중요한 예제 중 하나로,
다이나믹 프로그래밍 기법을 통해 효과적으로 해결할 수 있습니다.
이 문제를 통해 비트 마스킹과 메모이제이션이 어떻게 결합되어 강력한 해결책을 제공하는지를 이해할 수 있었습니다.
실제 코딩 인터뷰에서도 자주 등장하는 문제이므로 충분한 연습을 통해 숙련도를 높이는 것이 중요합니다.