백준 1620번 '나는야 포켓몬 마스터 이다솜' 시간초과 해결기

✨ Written with Claude

KEY POINT

양방향 매핑으로 검색 성능 최적화

문제 상황

백준 1620번 "나는야 포켓몬 마스터 이다솜" 문제를 vector → set → unordered_map 순으로 시도했는데 계속 시간초과가 발생했다.

tmi) 문제가 정말 쓸데없이(...) 긴데, 맨 밑 문장만 읽어도 된다. 사실 입력 파트부터 봐도 무방할 듯

초기 접근법과 문제점

이 문제의 요구사항은 쿼리 값(input)이 숫자면 도감의 해당 인덱스의 값(문자열)을,
input이 문자열이면 도감의 해당 값이 몇번째인지를 출력해야 된다.
이를 줄여서 각각 인덱스 검색, 문자열 검색이라고 하겠다.

1차 시도: Vector 사용

2차 시도: Set 사용

set<string> pokemons;
// 인덱스 검색: 인덱스에 접근 불가해서 순회 필요
// 예시 코드) 3번째 포켓몬을 찾고 싶다면 (0-based index) 
...
int target_index = 2; 

// 방법 1: iterator로 순회 
auto it = pokemons.begin(); 
advance(it, target_index); // iterator를 target_index만큼 이동
cout << "3번째 포켓몬: " << *it << '\n'; 

// 방법 2: 직접 순회 (더 직관적) 
int current_index = 0; 
for (const auto& pokemon : pokemons) { 
	if (current_index == target_index) { 
		cout << "3번째 포켓몬: " << pokemon << '\n'; 
		break; 
	} 
	current_index++; 
}
...
// 문자열 검색: find() 메소드 활용하여 O(logN)

3차 시도: Unordered Map (단방향)

...
unordered_map<int, string> pokemons;
for (int i = 0; i < N; i++) {
    string mon;
    cin >> mon;
    pokemons[i] = mon;
}
...
// 문제가 있던 부분
for (const auto& pair : pokemons) {
    if (pair.second == target) {  // 전체 맵을 순회하며 검색
        cout << pair.first + 1 << '\n';
        break;
    }
}
...

근본 원인 분석

문제의 핵심은 양방향 검색이 아닌 한 방향으로만 해결하려 했다는 점이다.

해결책: 양방향 매핑

핵심 아이디어

두 개의 Unordered Map을 사용하여 양방향 매핑을 구현한다.

제출한 코드

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    
    int N, M;
    cin >> N >> M;

    unordered_map<int, string> idxToName;  // 인덱스 -> 이름
    unordered_map<string, int> nameToIdx;  // 이름 -> 인덱스

    // 입력받으면서 양방향 매핑 구축 - O(N)
    for (int i = 1; i <= N; i++) {
        string name;
        cin >> name;
        idxToName[i] = name;
        nameToIdx[name] = i;
    }

    // 쿼리 처리 - O(M)
    for (int i = 0; i < M; i++) {
        string query;
        cin >> query;
        
        if (isdigit(query[0])) {
            cout << idxToName[stoi(query)] << '\n';  // O(1)
        } else {
            cout << nameToIdx[query] << '\n';        // O(1)
        }
    }
    
    return 0;
}

성능 비교

방식 시간복잡도 N=100,000, M=100,000일 때
Vector/Set (단방향) O(M×N) 10,000,000,000번 연산
Unorderd Map 양방향 매핑 O(N+M) 200,000번 연산

양방향 매핑을 쓰면 약 50,000배의 성능 향상이 가능하다.

추가 최적화 요소

1. 입출력 최적화

ios_base::sync_with_stdio(false);
cin.tie(NULL);

2. 검색이 더 빠른 자료구조?

핵심 레슨런

1. 양방향 검색의 함정

전체 성능은 가장 느린 부분에 의해 결정된다.
한 방향(인덱스→이름) 접근은 빠르게 해결되는데, 반대 방향(이름→인덱스) 접근에서 병목이 생기면? 양방향을 고려해보자.

2. 메모리 vs 시간 트레이드오프 고려하기

메모리를 2배 쓰는 대신 시간을 50,000배 절약할 수 있다면 명백하게 이득이다.
보통의 알고리즘 문제는 메모리 제한보다 시간 제한이 더 엄격한 경우가 많다고 한다.

3. unordered_map의 양면성

검색 연산이 평균적으로는 O(1)의 시간복잡도를 갖지만 최악의 경우 O(N)까지 갈 수 있다.
C++에서는 해시 충돌 방지를 위한 최적화가 잘 되어있어서 실제로는 안정적이라고 한다.
입출력 최적화와 함께 쓰면 효과가 배가 된다.

결론

이번 문제를 통해 양방향 검색도 고려해볼 수 있겠다는 유연한 사고 방식을 얻었다. 한쪽만 최적화하고 다른 쪽을 간과하면 전체 성능이 down된다.
특히 코테에서는 시간 제한이 더 빡세기 때문에 메모리를 조금 더 써서라도 시간을 확실하게 줄이는 것이 현명한 선택이다.
각 자료구조의 특성(장단점)을 잘 기억해두고 알고리즘 문제의 요구사항 시간복잡도 계산 시 적재적소에 적용해봐야겠다.