문제 이해
심사관마다 한 사람을 처리하는 시간이 다르고 모든 심사대는 처음에 비어 있습니다. 기다리는 n명이 어떤 심사대를 선택하든 상관없이, 전원이 심사를 마치는 데 필요한 최소 시간을 구해야 합니다.
예를 들어 n = 6, times = [7, 10]이면 7분 심사대는 28분 동안 4명, 10분 심사대는 2명을 처리할 수 있으므로 답은 28분입니다.
시뮬레이션이 어려운 이유
가장 직관적인 방법은 각 심사대가 다음에 비는 시간을 관리하며 사람을 한 명씩 배치하는 것입니다. 하지만 사람 수가 최대 10억 명이므로 한 사람마다 작업하는 방식은 입력 상한에서 사용할 수 없습니다.
답을 만드는 과정을 직접 재현하기보다, 어떤 시간 T가 답으로 충분한지 빠르게 판정할 수 있는지를 먼저 생각해야 합니다.
시간을 결정 문제로 바꾸기
한 사람을 심사하는 데 time분이 걸리는 심사관은 T분 동안 T / time명을 처리할 수 있습니다. 정수 나눗셈을 사용하는 이유는 해당 시간 안에 끝난 심사만 세어야 하기 때문입니다.
processed(T) = T / times[0]
+ T / times[1]
+ ...
processed(T) >= n 이면 T분 안에 모두 심사 가능
시간이 늘면 처리 가능한 사람 수는 줄지 않습니다. 따라서 어떤 시점 전까지는 불가능하고, 그 시점부터는 계속 가능합니다.
시간: 0 ... 27 | 28 ...
판정: 불가능 | 가능
이처럼 판정 결과가 한 번만 바뀌므로 이분 탐색으로 최초의 가능한 시간을 찾을 수 있습니다.
탐색 범위 정하기
최소 시간은 아무도 심사하지 않은 0분을 하한으로 둘 수 있습니다.
left = 0
상한은 가장 빠른 심사관이 n명을 혼자 처리하는 시간입니다. 실제로는 다른 심사관도 함께 일하므로 정답은 반드시 이 값 이하입니다.
right = min(times) * n
times를 오름차순 정렬하면 times[0]으로 가장 빠른 심사 시간을 얻을 수 있습니다.
가능한 첫 시간을 찾기
left와 right를 모두 정답 후보가 될 수 있는 닫힌 범위로 관리합니다. 중간 시간에 n명 이상 처리할 수 있다면 그 시간도 답 후보이므로 right = mid로 줄입니다.
while (left < right) {
long long mid = left + (right - left) / 2;
long long count = 처리 가능한 사람 수;
if (count >= n) {
right = mid;
} else {
left = mid + 1;
}
}
처리할 수 없다면 mid는 정답이 아니므로 제외하고 left = mid + 1로 이동합니다. 반복이 끝나 left == right가 되면 두 값이 가리키는 위치가 최초의 가능한 시간입니다.
예제로 따라가기
n = 6, times = [7, 10]이면 탐색 범위는 0부터 7 × 6 = 42까지입니다.
| mid | 처리 인원 | 판정 | 다음 범위 |
| 21 | 21 / 7 + 21 / 10 = 5 | 불가능 | [22, 42] |
| 32 | 32 / 7 + 32 / 10 = 7 | 가능 | [22, 32] |
| 27 | 27 / 7 + 27 / 10 = 5 | 불가능 | [28, 32] |
| 30 | 30 / 7 + 30 / 10 = 7 | 가능 | [28, 30] |
| 29 | 29 / 7 + 29 / 10 = 6 | 가능 | [28, 29] |
| 28 | 28 / 7 + 28 / 10 = 6 | 가능 | [28, 28] |
27분에는 5명만 처리할 수 있지만 28분에는 6명을 처리할 수 있으므로 최소 시간은 28분입니다.
절반만 통과한 이유
처음 작성한 코드에서는 반환형만 long long으로 두고 다음처럼 상한을 계산했습니다.
long long right = times[0] * n;
대입받는 변수가 long long이어도 곱셈의 두 피연산자가 모두 int이면 곱셈부터 int로 수행됩니다. 제한값에서는 최대 1018까지 필요하므로 중간 결과가 먼저 오버플로한 뒤 잘못된 값이 저장될 수 있습니다.
long long right = static_cast<long long>(times[0])
* static_cast<long long>(n);
곱셈 전에 피연산자를 long long으로 변환해야 실제 연산도 64비트 정수로 수행됩니다.
최종 코드
#include <vector>
#include <algorithm>
using namespace std;
long long solution(int n, vector<int> times) {
sort(times.begin(), times.end());
long long left = 0;
long long right = static_cast<long long>(times[0])
* static_cast<long long>(n);
while (left < right) {
long long mid = left + (right - left) / 2;
long long count = 0;
for (int time : times) {
count += mid / time;
}
if (count >= n) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
mid = left + (right - left) / 2 형태를 사용하면 left + right를 먼저 더할 때 생길 수 있는 오버플로도 피할 수 있습니다.
복잡도와 주의점
- 시간복잡도는 O(M log(N × T))
- 심사관 수를 M, 가장 빠른 심사 시간을 T라 하면 각 이분 탐색 단계에서 심사관을 한 번씩 순회합니다. 정렬의 O(M log M)도 필요합니다.
- 공간복잡도는 O(1)
- 정렬 구현의 내부 공간을 제외하면 탐색 경계와 처리 인원만 저장합니다.
- 값의 범위를 먼저 계산한다
- 정답과 상한이 최대 1018이므로 시간 관련 변수는 long long이어야 합니다.
- 가능한 경우에도 mid를 보존한다
- 최솟값을 찾으므로 가능한 mid 자체가 정답일 수 있습니다. 따라서 right = mid - 1이 아니라 right = mid를 사용합니다.
정리
- 최대 10억 명을 한 명씩 배치하는 시뮬레이션은 피한다.
- T분 동안 처리 가능한 인원은 각 심사대의 T / time을 더해 구한다.
- 시간이 늘수록 가능 여부가 불가능에서 가능으로 한 번만 바뀌므로 이분 탐색을 적용한다.
- 가장 빠른 심사관이 모두 처리하는 시간을 안전한 상한으로 사용한다.
- 큰 수의 곱셈은 연산 전에 long long으로 형변환해야 한다.
이번 문제에서는 대상을 직접 배치하는 대신 답의 후보인 시간을 탐색하는 관점 전환이 핵심이었습니다. 알고리즘이 맞아도 자료형 변환 시점 때문에 실패할 수 있다는 점까지 함께 확인했습니다.
'프로그래머스 문제 풀이' 카테고리의 다른 글
| [알고리즘 문제] 탐욕법 - 프로그래머스 단속 카메라(level 3) (0) | 2026.07.22 |
|---|---|
| [알고리즘 문제] 그래프 - 프로그래머스 방의 개수(level 5) (0) | 2026.07.21 |
| [알고리즘 문제] BFS - 프로그래머스 단어 변환(level 3) (0) | 2026.07.20 |
| [알고리즘 문제] BFS - 프로그래머스 가장 먼 노드(level 3) (0) | 2026.07.20 |
| [알고리즘 문제] 동적 계획법 - 프로그래머스 등대(level 3) (0) | 2026.07.20 |