이 문제는 겉으로 보면 차집합처럼 보이는데,
실제로는 딕셔너리 카운팅 문제다.
처음 보면 참가자 목록에서 완주자 목록 빼면 끝날 것 같아서
set부터 떠올리기 쉬운데,
동명이인 조건 때문에 그 접근은 바로 틀어진다.
핵심은 단순하다.
- 참가자 이름 개수를 세고
- 완주자 이름 개수를 빼고
- 마지막에 값이 남는 이름을 찾으면 된다
즉, 이 문제는 리스트 비교 문제가 아니라
이름 등장 횟수 비교 문제로 보는 게 맞다.
🔗 문제 링크
프로그래머스 42576 - 완주하지 못한 선수
https://school.programmers.co.kr/learn/courses/30/lessons/42576
문제 정리
문제 조건은 이렇다.
- 참가자 목록 participant
- 완주자 목록 completion
- 완주자는 참가자보다 항상 1명 적다
- 참가자 중 한 명만 완주하지 못했다
- 같은 이름이 여러 번 나올 수도 있다
여기서 바로 봐야 하는 건
동명이인 가능이다.
이 조건 때문에
이 문제는 set으로 풀면 안 된다.
왜냐면 set은 중복을 날려버리기 때문이다.
✅ 핵심 아이디어
이 문제는 흐름이 단순하다.
- 참가자 이름을 딕셔너리에 카운팅한다
- 완주자 이름을 보면서 카운트를 1씩 뺀다
- 마지막에 값이 0이 아닌 이름이 정답이다
즉, 이 문제는
누가 있는지가 아니라 누가 몇 번 나왔는지를 보는 문제다.
정답
def solution(participant, completion):
count = {}
for name in participant:
if name in count:
count[name] += 1
else:
count[name] = 1
for name in completion:
count[name] -= 1
for name in count:
if count[name] != 0:
return name
생각해야 하는 부분
이 문제는 해시 문제라고 분류되기도 하는데,
막상 구현할 때 중요한 건
이름을 key로 두고, 등장 횟수를 value로 관리하는 감각이다.
1. 왜 set이 아니라 dictionary인가
처음 보면 이런 식으로 풀고 싶어진다.
list(set(participant) - set(completion))
근데 이건 동명이인이 들어오는 순간 깨진다.
예를 들어
participant = ["leo", "kiki", "leo"]
completion = ["leo", "kiki"]
여기서 set으로 바꾸면 둘 다 {"leo", "kiki"}가 돼버린다.
그러면 누가 하나 더 있었는지 정보가 사라진다.
즉, 이 문제는 이름 자체보다 등장 횟수가 중요하다.
그래서 dictionary로 가야 한다.
2. 카운팅 구조로 보면 되게 단순해진다
참가자를 먼저 센다.
for name in participant:
if name in count:
count[name] += 1
else:
count[name] = 1
그다음 완주자를 보면서 뺀다.
for name in completion:
count[name] -= 1
그러면 마지막에
값이 남아 있는 이름이 완주하지 못한 선수다.
즉, 결국 이 문제는
차집합 문제처럼 보이지만 카운팅 문제다.
3. 정렬 풀이도 가능하긴 하다
이 문제는 딕셔너리 말고 정렬로도 풀 수 있다.
def solution(participant, completion):
participant.sort()
completion.sort()
for i in range(len(completion)):
if participant[i] != completion[i]:
return participant[i]
return participant[-1]
이 방식도 괜찮다.
정렬해두고 앞에서부터 비교하다가
처음 다른 지점이 나오면 그 사람이 완주 못 한 사람이다.
끝까지 같으면 마지막 참가자가 정답이다.
다만 문제를 이해하는 관점에서는
이름 등장 횟수를 직접 다루는 딕셔너리 풀이가 더 본질적이라고 봤다.
예외처리 / 주의할 점
⚠️ 1. 동명이인 조건을 절대 빼먹으면 안 된다
이 문제의 핵심은 사실 이 한 줄이다.
“동명이인이 있을 수 있다.”
이 조건 때문에 set 접근이 안 된다.
이걸 놓치면
문제 자체를 잘못 이해한 상태로 가게 된다.
⚠️ 2. value가 0이 아닌 이름을 찾아야 한다
참가자는 +1, 완주자는 -1 했으니
정상적으로 완주한 사람은 최종적으로 0이 된다.
즉 마지막에는 이렇게 본다.
for name in count:
if count[name] != 0:
return name
여기서 0이 아닌 이름이 바로 정답이다.
⚠️ 3. 딕셔너리 key / value 역할을 정확히 나눠야 한다
이 문제에서 딕셔너리 역할은 명확하다.
- key = 이름
- value = 등장 횟수
이걸 헷갈리면
카운팅 구조가 바로 흔들린다.
특히 처음 딕셔너리 문제를 풀 때
key/value 감각이 약하면 여기서 꼬이기 쉽다.
내 시행착오
이 문제에서 내가 처음 꼬였던 건 크게 세 가지였다.
1. set 차집합으로 먼저 가려고 했다
처음엔 제일 먼저 이 생각이 났다.
answer = list(set(regist).difference(set(goal)))
겉으로는 맞아 보인다.
근데 동명이인 조건 때문에 이 접근은 바로 무너진다.
이 문제는 “누가 있냐”보다
“누가 몇 번 있냐”가 중요하다는 걸 늦게 봤다.
2. 딕셔너리를 쓰려고 했는데 값 누적 구조가 안 잡혔다
중간에 딕셔너리로 풀려고도 했는데,
처음엔 이런 식으로 꼬였다.
dict = {}
key = 0
for i in participant:
dict[i] = key + 1
이렇게 하면
이름마다 값이 누적되는 게 아니라 계속 덮어써진다.
즉, 딕셔너리를 쓴다고 끝나는 게 아니라
누적 카운팅 구조로 봐야 했다.
3. 이 문제를 비교 문제로만 봤다
처음엔 참가자랑 완주자 리스트를 비교하는 문제라고만 생각했는데,
실제로는 그보다 카운팅 문제에 더 가까웠다.
이걸 보고 나니까 구조가 훨씬 간단해졌다.
- 참가자 +1
- 완주자 -1
- 남는 사람 찾기
결국 이렇게 정리됐다.
한 줄 정리
이 문제는 set 차집합으로 보면 틀리고,이름을 key, 등장 횟수를 value로 두는 dictionary 카운팅 문제로 보면 바로 정리된다.
'Coding Test > 문제풀이' 카테고리의 다른 글
| 프로그래머스 신고 결과 받기 | 구현 문제에서 문제 해석이 먼저인 이유 (0) | 2026.04.30 |
|---|---|
| 프로그래머스 파일명 정렬 | 정규식으로 HEAD, NUMBER, TAIL 나누는 방법 (0) | 2026.04.22 |
| 얼음 얼려 먹기 문제 정리 | DFS로 연결 요소 개수 세는 방법 (0) | 2026.04.21 |
| 백준 2178 미로 탐색 | 좌표형 BFS 구현과 최단거리 풀이 (0) | 2026.04.17 |
| 백준 18352 특정 거리의 도시 찾기 | BFS와 distance 배열 풀이 (0) | 2026.04.15 |
