백준 1764번 '듣보잡' 시간초과 해결기

✨ Written with Claude

KEY POINT

알맞은 자료구조 선택을 통한 알고리즘 시간복잡도 최적화

문제 상황


백준 1764번 "듣보잡" 문제를 vector로 해결했더니 시간초과가 발생했다.

초기 접근법과 문제점

// 문제가 있던 코드
for (int i = 0; i < M; i++) {
    for (int j = 0; j < N; j++) {
        if (unseens[i] == unheards[j]) {  // 이중 반복문으로 비교
            cnt++;
            result.push_back(unseens[i]);
        }
    }
}

시간복잡도: O(N × M)

근본 원인 분석


문제의 핵심은 탐색 방식에 있었다.

해결책: Set 자료구조 활용


Set의 장점

제출한 코드

#include <set>

set<string> unheardSet;
// N개의 입력을 set에 저장 - O(NlogN)
for (int i = 0; i < N; i++) {
    cin >> unheard;
    unheardSet.insert(unheard);
}

vector<string> result;
// M개의 입력에 대해 set에서 검색 - O(MlogN)
for (int i = 0; i < M; i++) {
    cin >> unseen;
    if (unheardSet.find(unseen) != unheardSet.end()) {
        result.push_back(unseen);
    }
}

개선된 시간복잡도: O(NlogN + MlogN) ~= O(NlogN)

성능 비교


방식 시간복잡도 N=500,000, M=500,000일 때
Vector (이중for문) O(N×M) 250,000,000,000번 연산
Set (find() 활용) O((N+M)logN) 약 19,000,000번 연산

벡터를 안 쓰고 집합을 쓰면 약 13,000배의 성능 향상이 이뤄진다.

추가로 최적화 가능한 경우


1. unordered_set (해시테이블)

#include <unordered_set>
...
unordered_set<string> unheardSet;

for (int i = 0; i < N; i++) {
    cin >> unheard;
    unheardSet.insert(unheard);
}

for (int i = 0; i < M; i++) {
    cin >> unseen;
    if (unheardSet.find(unseen) != unheardSet.end()) {
        result.push_back(unseen);
    }
}
...

2. set 두 개 생성 후 교집합 찾기

...
set<string> result;
set_intersection(unheardSet.begin(), unheardSet.end(),
                 unseenSet.begin(), unseenSet.end(),
                 inserter(result, result.begin()));
...

핵심 레슨런


1. 자료구조 선택의 중요성

2. 시간복잡도 분석 습관 들이기

3. 최적화 우선순위

  1. 알고리즘 설계
  2. 자료구조 선택
  3. 구현 최적화

4. 문제 해결 프로세스

문제 분석 → 시간복잡도 예측 → 자료구조 선택 → 구현 → 테스트

결론


단순히 "시간초과가 났으니 다른 자료구조를 써야겠네"가 아니라, 기존의 방식은 왜 느린지 분석하고 새로운 방식은 어떤 특성 때문에 필요한 것인지 파악한 후 적절한 도구를 선택해야겠다.

이번 경험을 통해 탐색이 빈번한 상황에서는 set/map 계열 자료구조를 우선 고려해볼 수도 있겠다는 교훈을 얻었다.