문제 보기 · Level 3 · 2021 KAKAO BLIND RECRUITMENT · 정답률 37% · 푼 사람 5,638명 (2026-09-30 기준)1
문제
동영상 길이 play_time, 광고 길이 adv_time(둘 다 HH:MM:SS), 시청 기록 logs(H1:M1:S1-H2:M2:S2)가 주어진다. 광고를 동영상의 어느 시각에 시작해 끼워 넣을지 정한다. 광고가 나오는 동안 시청자들의 재생 시간을 모두 더한 값(누적 재생 시간)이 가장 큰 시작 시각을 HH:MM:SS로 돌려준다. 그런 곳이 여러 곳이면 가장 빠른 시각을 돌려준다. 재생 시간은 “끝 시각 − 시작 시각”이다. 1초부터 10초까지 봤다면 9초다.
- 동영상·광고 길이 최대 99:59:59, 광고 ≤ 동영상
- 기록 최대 300,000개
풀이
막히면 한 단계씩 펼친다.
필요한 개념
- 시간·날짜 계산:
HH:MM:SS를 초 하나로 바꾼다. 99:59:59는 359,999초다. - 누적 합과 차분 배열: 차분으로 “1초마다 보는 사람 수”를 만들고, 누적 합으로 “광고 구간의 합”을 뺄셈 한 번에 구한다.
- 미적분의 기본정리: 누적 재생 시간은 “1초마다 보는 사람 수”를 광고 구간에서 적분한 값이다. 이 값이 누적 합 두 값의 차가 되는 것은 기본정리 2부(구간의 적분 = 끝값의 차)와 같은 모양이다.
1단계 · 작은 예로 손으로 풀기
동영상 10초, 광고 3초, 기록은 1~4초, 2~6초, 5~9초다. “i초 ~ i + 1초”를 한 칸으로 보고, 칸마다 보는 사람 수를 센다.
| 칸 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 보는 사람 | 0 | 1 | 2 | 2 | 1 | 2 | 1 | 1 | 1 | 0 |
광고를 t초에 시작하면 칸 t, t + 1, t + 2의 합이 누적 재생 시간이다. t = 0이면 3, t = 1이면 5, t = 2, 3도 5다. 가장 큰 값 5가 처음 나오는 t = 1, 곧 00:00:01이 답이다.
2단계 · 가장 단순한 방법과 그 속도
시작 시각 t마다 모든 기록과 겹치는 길이를 더한다. t는 최대 36만 가지, 기록은 30만 개라 약 1,000억 번이다. 기록마다 그 구간의 칸을 하나씩 +1 해도, 긴 기록 30만 개면 역시 1,000억 번 가까이 된다.
3단계 · 핵심 아이디어
- 차분으로 보는 사람 수 만들기: 기록 s ~ e마다 cnt[s] += 1, cnt[e] −= 1만 적는다. 끝 시각 e의 칸은 보지 않으니 e에서 뺀다. 앞에서부터 누적하면 cnt[i]가 칸 i의 보는 사람 수가 된다.
- 누적 합으로 구간 합 구하기: S[t]를 “칸 0 ~ t − 1의 합”으로 둔다. 광고를 t초에 시작하면 칸 t ~ t + A − 1을 덮으니 누적 재생 시간은 S[t + A] − S[t]다(A는 광고 길이). +1, −1 표시는 보는 사람 수의 차분이라 도함수 자리이고, 누적은 적분 자리다(합 ↔ 적분).
- 시작 시각 모두 보기: t = 0 ~ P − A(P는 동영상 길이)를 보며 가장 큰 값을 고른다. 같은 값이면 먼저 나온 t를 남기려고
>로 비교한다.
4단계 · 코드 흐름
- 동영상 길이 P, 광고 길이 A를 초로 바꾼다.
- 길이 P + 1인 cnt에 기록마다 +1, −1을 적고 앞에서부터 누적한다.
- cnt를 한 번 더 누적해 S를 만든다(S[0] = 0).
- t = 0 ~ P − A에서 S[t + A] − S[t]가 가장 큰 첫 t를 찾아
HH:MM:SS로 바꾼다.
5단계 · 파이썬 코드
def to_sec(t):
h, m, s = map(int, t.split(":"))
return h * 3600 + m * 60 + s
def solution(play_time, adv_time, logs):
P, A = to_sec(play_time), to_sec(adv_time)
cnt = [0] * (P + 1)
for log in logs:
s, e = log.split("-")
cnt[to_sec(s)] += 1
cnt[to_sec(e)] -= 1 # 끝 시각의 1초는 보지 않는다
for i in range(1, P + 1): # cnt[i]: i초 ~ i+1초를 보는 사람 수
cnt[i] += cnt[i - 1]
S = [0] * (P + 1) # S[t]: 0초 ~ t초의 누적 재생 시간
for i in range(P):
S[i + 1] = S[i] + cnt[i]
best, best_t = -1, 0
for t in range(P - A + 1):
v = S[t + A] - S[t]
if v > best: # 같으면 앞의 것을 남긴다
best, best_t = v, t
return f"{best_t // 3600:02d}:{best_t % 3600 // 60:02d}:{best_t % 60:02d}"
map(int, t.split(":")):"01:30:59"를["01", "30", "59"]로 나눈 뒤 모두 정수로 바꾼다.f"{x:02d}": 정수를 두 자리로, 모자라면 앞에 0을 붙여 쓴다. 7 →07.best_t % 3600 // 60: 한 시간을 넘는 부분을 버린 뒤 60으로 나눈 몫, 곧 분이다.
6단계 · 실행 추적
1단계의 예(P = 10, A = 3)
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 표시 cnt | 0 | 1 | 1 | 0 | −1 | 1 | −1 | 0 | 0 | −1 | 0 |
| 누적한 cnt | 0 | 1 | 2 | 2 | 1 | 2 | 1 | 1 | 1 | 0 | 0 |
| S[i] | 0 | 0 | 1 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 11 |
| t | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| S[t + 3] − S[t] | 3 | 5 | 5 | 5 | 4 | 4 | 3 | 2 |
5가 처음 나오는 t = 1이 답이다. 문제의 예시 1도 같은 계산으로 01:30:59에서 36분(2,160초)이 나온다.
7단계 · 복잡도와 흔한 실수
기록 L개와 동영상 P초에 \(O(L + P)\)다. 기록 30만 개, 동영상 99:59:59로 재 보니 0.2초였다.
- 끝 시각의 칸까지 보는 것으로 세어(cnt[e + 1]에서 빼기) 한 칸씩 어긋난다.
- 같은 값에서
>=로 비교해 더 늦은 시각을 고른다. - 시작 시각을 P − A + 1개가 아니라 P − A개까지만 본다. 광고가 동영상 끝에 딱 맞게 끝나는 자리도 된다.
- 답을
1:0:5처럼 0을 채우지 않고 쓴다.
-
프로그래머스 스쿨, “광고 삽입” (https://school.programmers.co.kr/learn/courses/30/lessons/72414), 2026-09-30 조회. ↩