Koko Eating Bananas
Koko에게는 바나나 더미가 n개 있으며, piles[i]는 i번째 더미에 있는 바나나의 개수이고, 경비원이 돌아오기까지 h시간이 남아 있습니다. Koko는 시간당 바나나를 먹는 속도 k를 정수로 하나 선택해 계속 유지합니다. 매시간 한 더미에서 바나나를 k개 먹습니다. 그 더미에 남은 바나나가 k개보다 적으면 남은 바나나를 모두 먹고, 시간이 끝날 때까지 쉽니다. h시간 안에 모든 더미를 다 먹을 수 있는 가장 작은 속도 k를 반환하세요.
함수
- pilesinteger-array
- 각 더미에 있는 바나나의 개수
- hinteger
- Koko가 가진 시간
- 반환값integer
- h시간 이내에 모든 바나나 더미를 먹어 치울 수 있는 최소 정수 먹는 속도(시간당 바나나 수)
제약 조건
1 ≤ piles.length ≤ 50001 ≤ piles[i] ≤ 109piles.length ≤ h ≤ 109이므로 항상 답이 존재합니다.
예제
- 입력
- piles = [4, 10, 7, 3]h = 6
- 출력
- 5
- 설명
- 속도 5에서 더미 4, 10, 7, 3은 각각 1, 2, 2, 1시간이 걸립니다. 총 6시간으로, 제한 시간에 맞습니다. 속도 4에서는 각각 1, 3, 2, 1시간이 걸리며, 총 7시간으로 1시간 초과합니다.
- 입력
- piles = [30, 11, 23, 4, 20]h = 5
- 출력
- 30
- 설명
- 다섯 개의 더미와 다섯 시간이라면 더미 하나당 정확히 한 시간이므로, 속도는 가장 큰 더미인 30개를 한 시간 안에 처리할 수 있어야 합니다. 속도가 29라면 그 더미를 처리하는 데 두 시간이 필요합니다.
- 입력
- piles = [5, 9, 2]h = 20
- 출력
- 1
- 설명
- 속도 1에서는 더미를 처리하는 데 5 + 9 + 2 = 16시간이 걸리므로, 20시간 이내입니다. 속도 1보다 느릴 수는 없으므로 답은 1입니다.
제출 시 숨은 테스트 +22개
후속 질문
A twin problem: Koko에게는 d일이 주어지고, 매일 k개의 바나나라는 일일 한도에 맞춰 주어진 순서대로 더미를 통째로 먹습니다. 가장 작은 k는 얼마이며, 이분 탐색의 어떤 두 부분이 바뀌나요?
힌트
하나씩 열어 보세요. 열 때마다 조금 더 알려 줍니다.
속도
k를 하나 정하세요. 코코가 한 시간 안에 바나나 더미를 바꾸지 않는다고 할 때,p개의 바나나 더미를 그 속도로 먹는 데 몇 시간이 걸리나요? 모든 더미를 먹는 데는 몇 시간이 걸리나요?속도
k가 시간 안에 끝낼 수 있다면, 그보다 더 빠른 모든 속도도 가능합니다. 가능한 속도들은 답에서 시작하는 하나의 연속된 구간을 이룹니다.속도 1부터 가장 큰 더미의 속도까지 이진 탐색을 합니다. 한 번 순회하면서 중간 속도에서 필요한 시간을 계산합니다. 그 시간이
h이내라면 정답은 중간 속도 이하이고, 그렇지 않으면 중간 속도보다 큽니다.
풀이
여기서 답은 배열에서의 위치가 아니라 속도이며, 이 점 때문에 이분 탐색을 할 수 있습니다. 속도 하나를 확인하려면 더미를 한 번씩 훑으면 됩니다. 확인 결과도 순서가 맞아떨어집니다. 속도 k로 제시간에 끝낼 수 있다면, 그보다 빠른 모든 속도로도 제시간에 끝낼 수 있습니다. 따라서 속도 1부터 가장 큰 더미까지 이분 탐색하면 약 30번의 확인이 필요하지만, 속도를 하나씩 시도하면 10억 번이 필요할 수 있습니다.
1부터 시작해 모든 속도를 시도해 보세요
정답이지만 가장 큰 테스트에서는 끝나지 않음
핵심 아이디어
질문 하나로 시작해 보세요. 바나나가 p개 있는 더미를 속도 k로 먹는 데 얼마나 걸릴까요? 코코는 한 시간에 k개를 먹고 같은 시간 안에는 다른 더미로 옮겨 가지 않으므로, 더미 하나를 먹는 데는 올림한 p / k시간이 걸립니다. 속도가 4일 때 바나나가 10개인 더미는 3시간이 걸립니다. 4개, 4개를 먹고 나서 2개를 먹은 뒤 쉬는 식입니다. 모든 더미에 걸리는 시간을 더하고 그 합계를 h와 비교하세요.
이제 속도를 1, 2, 3 순서로 시도해 보고, 합계가 h 이내인 첫 번째 속도를 반환하세요. 더 느린 속도는 모두 시도했지만 조건을 만족하지 못했으므로, 이렇게 하면 가장 작은 속도가 됩니다. 반복문은 항상 멈춥니다. 가장 큰 더미의 크기와 같은 속도라면 각 더미를 한 시간씩 먹으면 되고, h는 더미 개수 이상이기 때문입니다.
문제는 반복문이 얼마나 오래 실행될 수 있느냐입니다. 바나나가 거의 10^9개씩 들어 있는 더미가 5000개이고 h = 5000이라면, 답은 10^9에 가까우므로 반복문은 약 10억 번 실행되고 각 확인 작업은 더미 5000개를 모두 읽습니다. 총 약 5 × 10^12단계입니다. 여기서 m은 가장 큰 더미의 크기입니다.
알고리즘
speed = 1로 설정합니다.- 이 속도에서 시간을 셉니다. 각 더미마다
(pile + speed-1) / speed를 더하며, 합계는 64비트로 저장합니다. - 합계가
h이하이면speed를 반환합니다. - 그렇지 않으면
speed에 1을 더하고 다시 셉니다.
def minEatingSpeed(piles, h):
speed = 1
while True:
hours = 0
for pile in piles:
hours += (pile + speed - 1) // speed # a started pile costs a whole hour
if hours <= h:
return speed
speed += 1속도에 대한 이진 검색
핵심 아이디어
1부터 가장 큰 더미까지의 모든 속도를 “이 속도라면 제시간에 끝낼 수 있는가?”라는 질문에 대한 답의 행으로 생각해 보세요. 속도가 빨라질수록 각 더미를 처리하는 데 걸리는 시간은 같거나 줄어드므로, 전체 시간은 줄어들 수밖에 없습니다. 따라서 답의 행은 아니요, 아니요, 아니요, 그리고 답부터는 예로 이어지며, 다시 아니요로 돌아가지 않습니다. 찾으려는 것은 첫 번째 예이고, 아니요와 예로 이루어진 정렬된 행은 바로 이진 탐색으로 절반씩 나누는 대상입니다.
항상 답을 포함하는 lo부터 hi까지의 범위를 유지하세요. 범위는 1부터 가장 큰 더미로 시작합니다. 가장 큰 더미의 속도는 더미마다 한 시간이 걸리고 h는 이를 감당할 수 있으므로, 이 범위는 안전합니다. 중간 속도 mid를 확인하세요. 시간 안에 끝낼 수 있다면 답은 mid이거나 그보다 느린 속도이므로 hi = mid로 설정하고 mid를 범위에 남겨 둡니다. 시간 안에 끝낼 수 없다면 더 느린 모든 속도도 실패하므로 lo = mid + 1로 설정합니다. lo와 hi가 같아지면 그 속도가 답입니다.
첫 번째 예시를 따라가 보세요. 더미는 4, 10, 7, 3이고 h = 6입니다. 범위는 1부터 10입니다. 속도 5에서는 1 + 2 + 2 + 1 = 6시간이 걸려 조건을 만족하므로 범위는 1부터 5가 됩니다. 속도 3에서는 2 + 4 + 3 + 1 = 10시간이 걸려 너무 오래 걸리므로 범위는 4부터 5가 됩니다. 속도 4에서는 1 + 3 + 2 + 1 = 7시간이 걸려 여전히 너무 오래 걸리므로 범위는 5부터 5가 되고, 답은 5입니다.
각 확인마다 범위가 절반으로 줄어드므로, 최대 10^9개의 속도가 있는 범위도 약 30번 확인하면 됩니다. 확인당 더미가 5000개라면 약 150000단계로 끝나며, 수조 단계가 필요한 것과 비교하면 훨씬 적습니다.
알고리즘
lo = 1로 설정하고hi를 가장 큰 더미로 설정합니다.lo < hi인 동안mid = lo + (hi - lo) / 2를 구합니다.- 속도
mid에서 걸리는 시간을 계산합니다. 모든 더미에 대해(pile + mid-1) / mid를 더하고, 합계는 64비트로 저장합니다. - 합계가
h이하이면hi = mid로 설정하고, 그렇지 않으면lo = mid + 1로 설정합니다. - 루프가 끝나면
lo를 반환합니다.
def minEatingSpeed(piles, h):
lo, hi = 1, max(piles) # the largest pile always works: one hour per pile
while lo < hi:
mid = lo + (hi - lo) // 2
hours = 0
for pile in piles:
hours += (pile + mid - 1) // mid
if hours <= h:
hi = mid # mid works, so the answer is mid or slower
else:
lo = mid + 1 # mid is too slow, so the answer is faster
return lo
함정과 경계 사례
검색 자체는 간단합니다. 버그는 시간 계산과 범위의 경계에서 발생합니다.
- 시간 계산 오버플로. 속도가 1이면 바나나가
10^9개씩 있는 더미 5000개를 처리하는 데5 × 10^12시간이 걸립니다. 이는 약2.1 × 10^9인 32비트 한계를 훨씬 넘어섭니다. 합계가 오버플로되면 실제보다 작게 나와 너무 느린 속도가 검사에 통과할 수 있습니다. 64비트 정수로 계산하거나 합계가h를 넘는 즉시 계산을 중단하세요. - 잘못된 방향으로 반올림. 정수 나눗셈은 내림하므로
10 / 4는 2가 되지만, 해당 더미를 처리하는 데는 3시간이 걸립니다.(pile + k-1) / k를 사용해 올림하세요. - 범위를 0에서 시작. 그러면
mid가 0이 되어 시간 계산에서 0으로 나누게 될 수 있습니다. 실제로 가능한 가장 느린 속도는 1입니다. mid가 조건을 만족할 때hi를mid - 1로 옮기기. 그러면 정답 자체를 범위에서 제외할 수 있습니다. 처음으로 조건을 만족하는 속도를 찾을 때는hi = mid로mid를 유지하고lo < hi인 동안 반복하세요.hi를 가장 큰 더미보다 작게 설정.h가 더미 개수와 같으면 그보다 느린 모든 속도가 실패할 수 있으므로, 검색 결과로 조건을 만족하지 않는 속도가 반환될 수 있습니다.
자주 묻는 질문4
코코가 바나나를 먹는 데 걸리는 시간 복잡도는 얼마인가요?
이분 탐색은 O(n log m) 시간에 실행됩니다. 여기서 n은 더미의 개수이고 m은 가장 큰 더미의 크기입니다. 각 확인 과정에서 모든 더미를 한 번씩 읽고, 확인할 때마다 속도 범위가 절반으로 줄어들므로 확인 횟수는 약 log2(m)번입니다. m = 10^9일 때는 30번입니다. 추가 공간은 O(1)입니다.
먹는 속도에 이진 탐색을 적용할 수 있는 이유는 무엇인가요?
이진 탐색에는 답이 정렬되는 예 또는 아니요 질문이 필요합니다. "Koko가 속도 k로 끝낼 수 있을까?"가 그런 질문입니다. 더 빠른 속도에서는 각 더미의 p / k를 올림한 값이 k가 커질수록 작아지기만 하므로 필요한 시간이 더 늘어나지 않습니다. 따라서 정답보다 낮은 모든 속도에서는 실패하고, 정답 이상의 모든 속도에서는 성공하며, 탐색은 그 경계를 찾습니다.
속도의 하한과 상한은 얼마인가요?
상한은 가장 큰 더미입니다. 그 속도에서는 모든 더미를 정확히 1시간씩 먹을 수 있고, h는 더미의 개수 이상이므로 항상 들어맞습니다. 더 빠른 속도여도 더미마다 1시간이 필요하므로, 그보다 높은 속도를 탐색해도 얻는 것이 없습니다. 하한은 1이며, 이를 전체 바나나 개수를 h로 나눈 뒤 올림한 값으로 더 높일 수 있습니다. Koko는 한 시간에 최대 k개의 바나나를 먹기 때문입니다.
정수로 어떻게 나누고 올림하나요?
정수 나눗셈을 사용해 (p + k-1) / k를 사용하세요. k-1을 더하면 나머지가 있는 경우 다음 k의 배수로 올림되고, 정확한 배수는 그대로 유지됩니다. 속도가 4일 때 10은 13 / 4 = 3이 되고, 속도가 4일 때 8은 11 / 4 = 2가 됩니다. 큰 값이 잘못 반올림될 수 있는 부동 소수점 연산을 피할 수 있습니다.
비슷한 문제
같은 아이디어를 쓰는 문제입니다. 두세 개를 풀면 패턴이 몸에 익습니다.
Python
def minEatingSpeed(piles, h):
# 여기에 코드를 작성하세요케이스 1
케이스 2
케이스 3
입력
piles = [4, 10, 7, 3] h = 6
기대값
5