각 로그에는 요청의 응답 완료 시각과 처리 시간이 주어진다.
모든 요청의 처리 구간을 구한 뒤, 임의의 시점부터 시작하는 1초 구간에 포함되는 요청 수의 최댓값을 반환해야 한다.
요청은 1초 구간 안에서 완료될 필요가 없다. 처리 구간과 1초 구간이 조금이라도 겹치면 해당 요청은 처리량에 포함된다.
모든 시각을 직접 확인할 필요는 없다. 최대 처리량을 만드는 1초 구간은 어떤 요청의 완료 시각에서 시작하도록 잡을 수 있다.
어떤 1초 구간에 여러 요청이 겹친다고 하자. 그 요청들 가운데 가장 먼저 완료되는 요청의 완료 시각까지 구간의 시작점을 오른쪽으로 이동해도 기존 요청은 사라지지 않는다.
따라서 각 요청의 완료 시각을 1초 구간의 시작점으로 사용해 겹치는 요청 수를 계산하면 충분하다.
시간을 float으로 계산하면 소수 표현 오차 때문에 구간의 경계에서 잘못된 결과가 나올 수 있다.
문제의 시간 정밀도는 밀리초이므로 모든 시각을 정수 밀리초로 변환한다.
01:02:03.456
= 1시간 + 2분 + 3초 + 456밀리초
= ((1 × 60 + 2) × 60 + 3) × 1000 + 456
처리 시간도 문자열을 직접 분리하여 밀리초 정수로 만든다.
완료 시각을 end, 처리 시간을 duration이라고 하면 요청의 시작 시각은 다음과 같다.
start = end - duration + 1
처리 시간은 시작 시각과 완료 시각을 모두 포함하기 때문에 1ms를 더해야 한다.
예를 들어 완료 시각이 1000ms이고 처리 시간이 1ms라면 처리 구간은 [1000, 1000]이어야 한다.
start = 1000 - 1 + 1 = 1000
1초 구간의 시작 시각을 window_start라고 하면 밀리초 단위의 구간은 다음과 같다.
[window_start, window_start + 999]
요청의 처리 구간 [start, end]가 이 구간과 겹치려면 다음 두 조건을 모두 만족해야 한다.
end >= window_start
start <= window_start + 999
각 후보 완료 시각에 대해 모든 요청을 확인하면서 이 조건을 만족하는 요청의 개수를 센다.
start = end - duration + 1로 처리 시작 시각을 계산한다.[start, end] 구간을 저장한다.def solution(lines):
intervals = []
def to_milliseconds(time_text):
hour, minute, second_text = time_text.split(":")
second, millisecond = second_text.split(".")
return (
(int(hour) * 60 * 60 + int(minute) * 60 + int(second))
* 1000
+ int(millisecond)
)
def duration_to_milliseconds(duration_text):
number = duration_text[:-1] # 마지막 's' 제거
if "." not in number:
return int(number) * 1000
second, millisecond = number.split(".")
millisecond = millisecond.ljust(3, "0")
return int(second) * 1000 + int(millisecond)
for line in lines:
_, time_text, duration_text = line.split()
end = to_milliseconds(time_text)
duration = duration_to_milliseconds(duration_text)
start = end - duration + 1
intervals.append((start, end))
max_throughput = 0
for _, window_start in intervals:
window_end = window_start + 999
throughput = 0
for start, end in intervals:
if end >= window_start and start <= window_end:
throughput += 1
max_throughput = max(max_throughput, throughput)
return max_throughput
to_milliseconds()는 hh:mm:ss.sss 형식의 시각을 자정부터 지난 밀리초로 변환한다.
모든 로그가 같은 날짜에 속하므로 날짜는 계산에 사용할 필요가 없다.
duration_to_milliseconds()는 마지막의 s를 제거한 뒤 정수부와 소수부를 분리한다.
소수부는 최대 세 자리이므로 ljust(3, "0")을 사용해 밀리초 세 자리로 맞춘다.
0.1s -> 100ms
0.12s -> 120ms
0.123s -> 123ms
2s -> 2000ms
두 닫힌 구간이 겹치지 않는 경우는 다음 두 가지다.
이를 반대로 표현하면 코드의 겹침 조건이 된다.
if end >= window_start and start <= window_end:
각 로그의 완료 시각과 처리 시간을 밀리초 정수로 변환하고, 양 끝을 포함한다는 조건에 따라 정확한 처리 구간 [start, end]를 구한다.
임의의 최대 처리량 구간에 포함된 요청 중 가장 먼저 완료되는 요청의 완료 시각까지 구간 시작점을 이동해도 포함된 요청 수는 줄어들지 않는다. 따라서 적어도 하나의 최대 처리량 구간은 어떤 요청의 완료 시각에서 시작한다.
알고리즘은 모든 요청의 완료 시각을 후보로 검사하며, 각 후보에서 정확한 구간 겹침 조건을 만족하는 요청을 모두 센다. 그러므로 계산된 최댓값은 가능한 모든 1초 구간의 최대 처리량과 같다.
로그의 개수를 N이라고 하면, N개의 완료 시각마다 모든 요청 N개를 확인한다.
O(N^2)
N은 최대 2,000이므로 약 400만 번의 구간 비교로 충분히 처리할 수 있다.
각 요청의 시작 시각과 완료 시각을 저장하므로 공간 복잡도는 다음과 같다.
O(N)
+1ms가 필요하다.[시작, 시작 + 999ms]로 계산한다.이 문제는 로그의 처리 시간을 정확한 닫힌 구간으로 변환한 뒤, 1초 구간과 겹치는 요청의 최댓값을 구하는 문제다.
모든 시간을 정수 밀리초로 변환하고 요청의 완료 시각만 후보로 검사하면, 경계값 오류 없이 O(N^2)에 해결할 수 있다.