🔗 문제
https://www.acmicpc.net/problem/1141
📌 문제 요약
입력: 첫째 줄에 단어의 개수 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 단어가 주어진다. 단어는 알파벳 소문자로만 이루어져 있고, 길이는 최대 50이다. 집합에는 같은 단어가 두 번 이상 있을 수 있다.
출력: 접두사X 집합인 부분집합의 최대 크기를 출력한다.
💡 접근 방법
문제 유형: 정렬 + 그리디 (문자열)
“접두사 관계인 단어 쌍이 하나도 없는 최대 부분집합”을 찾는 문제다.
N이 50, 단어 길이도 50이라 사실 모든 쌍을 다 비교해도(O(N²·L)) 통과하지만,
정렬을 한 번 해두면 비교 대상이 확 줄어든다.
왜 정렬인가?
사전순으로 정렬하면 abc의 접두사인 ab는 반드시 abc보다 앞에 온다.
게다가 ab와 abc 사이에 낀 단어가 있다면 그 단어도 ab로 시작할 수밖에 없다.
즉 어떤 단어가 누군가의 접두사라면, 바로 다음 단어의 접두사이기도 하다.
그래서 인접한 두 개씩만 비교하면 충분하다.
그다음은 그리디다. words[i]가 words[i+1]의 접두사라면 둘 중 하나는 버려야 하는데,
짧은 쪽(= 앞쪽)을 버리는 게 항상 이득이다. 짧은 단어는 뒤에 오는 더 많은 단어의
접두사가 될 수 있어서 남겨두면 손해만 커지기 때문이다.
N개의 문자열을 입력 받고 오름차순으로 정렬한 뒤,
다음 인덱스의 문자열에 현재 문자열이 접두사로 사용되는지 확인하는 방식으로 구현했다.
참고로 이 풀이는 중복 단어도 자동으로 처리된다.
같은 단어는 정렬 후 붙어 있고, 자기 자신은 자기 자신의 접두사이므로 앞쪽이 그대로 걸러진다.
⚠️ 처음에 했던 실수
if(words[i + 1].length() >= len && words[i + 1].substr(0,len) == words[i])
continue;
해당 부분의 코드가 원래는
if(words[i + 1].length() < len)
continue;
if(words[i + 1].substr(0,len) == words[i])
continue;
이런 코드로 작성했었다.
의도는 다음 문자열보다 길이가 길면 원치않는 방향으로 조건 검사를 할까봐 다음으로 넘기도록 하는 조건이였는데
다음 문자열이 현재 문자열보다 짧으면 포함될일이 없다고 다시 생각해서 조건문을 수정했다.
💻 코드
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int N;
string words[51];
int main()
{
int ans = 0;
cin >> N;
for(int i = 0; i < N; ++i)
{
cin >> words[i];
}
// 사전순 정렬: 접두사 관계는 이제 인접한 쌍에서만 나타난다
sort(words, words + N);
// 마지막 원소는 뒤에 비교 대상이 없으므로 루프에서 제외
for(int i = 0; i < N - 1; ++i)
{
int len = words[i].length();
// words[i]가 words[i+1]의 접두사면 짧은 쪽(words[i])을 버린다.
// 길이 조건을 먼저 보는 이유: 다음 단어가 더 짧으면 접두사일 수 없고,
// substr(0, len) 자체가 불필요한 연산이 되기 때문
if(words[i + 1].length() >= len && words[i + 1].substr(0,len) == words[i])
continue;
ans++;
}
ans++; // 루프에서 뺀 마지막 단어는 항상 답에 포함된다
cout << ans;
return 0;
}
⏱️ 시간·공간 복잡도
N = 단어 개수(≤ 50), L = 단어 최대 길이(≤ 50)
| 구간 | 복잡도 | 설명 |
|---|---|---|
| 정렬 | O(N·L log N) | 비교가 문자열 비교라 한 번에 최대 L글자를 본다 |
| 접두사 검사 루프 | O(N·L) | N-1번 반복 × substr + 비교 O(L) |
| 전체 시간 | O(N·L log N) | 정렬이 지배한다 |
| 공간 | O(N·L) | string words[51] 배열, 최대 50×50자 |
N과 L이 둘 다 50이라 실제로는 연산량이 수천 번 수준이다. 제한이 아주 널널한 문제.
🔀 다른 풀이와 비교
| 방식 | 시간복잡도 | 메모 |
|---|---|---|
| 모든 쌍 비교 (O(N²)) | O(N²·L) | N ≤ 50이라 이것도 통과. 정렬 없이 직관적으로 짤 수 있다 |
| 정렬 후 인접 비교 (이 풀이) | O(N·L log N) | 코드가 짧고, N이 커져도 버틴다 |
| 트라이(Trie) | O(N·L) | 삽입하면서 “끝 노드가 다른 단어의 중간인지” 판정. N이 훨씬 컸다면 이게 정석 |
이 문제 크기에선 셋 다 통과하지만, 정렬 방식이 구현량 대비 확장성이 제일 좋다고 느꼈다.