4 분 소요

06 무지의 먹방 라이브

그리디란?

  • 현재 상황에서 가장 좋아 보이는 것만을 선택하는 알고리즘이다.
  • 정확한 답을 도출하지 못할 수도 있지만 코딩테스트에서는 대부분 ‘최적의 해’를 찾는 문제가 출제되므로 그리디 알고리즘의 정당성을 고민하면서 문제 해결 방안을 떠올려야한다.
  • 다양한 그리디 알고리즘
    • 다익스트라 최단 경로 알고리즘
      • 암기가 필요한 알고리즘이다. 팀 노트 필요!
      • 특정 노드까지의 최단 거리 계산한 다음 메모리에 저장하고 나중에 필요할 때 다시 꺼내 본다는 점에서 다이나믹 프로그래밍으로 분류하기도 한다.
    • 크루스칼 알고리즘
      • 우선순위 큐 (최소 힙, 최대 힙)
      • 여러 개의 값들 중 최댓값이나 최솟값을 빠르게 찾아내도록 만들어진 자료구조이다.

문제

평소 식욕이 왕성한 무지는 자신의 재능을 뽐내고 싶어 졌고 고민 끝에 카카오 TV 라이브로 방송을 하기로 마음먹었다.

https://grepp-programmers.s3.amazonaws.com/files/production/10f4f72c93/1d932bfc-8082-4b7e-b30d-ab46bf71a9f2.png

그냥 먹방을 하면 다른 방송과 차별성이 없기 때문에 무지는 아래와 같이 독특한 방식을 생각해냈다.

회전판에 먹어야 할 N 개의 음식이 있다.각 음식에는 1부터 N 까지 번호가 붙어있으며, 각 음식을 섭취하는데 일정 시간이 소요된다.무지는 다음과 같은 방법으로 음식을 섭취한다.

  • 무지는 1번 음식부터 먹기 시작하며, 회전판은 번호가 증가하는 순서대로 음식을 무지 앞으로 가져다 놓는다.
  • 마지막 번호의 음식을 섭취한 후에는 회전판에 의해 다시 1번 음식이 무지 앞으로 온다.
  • 무지는 음식 하나를 1초 동안 섭취한 후 남은 음식은 그대로 두고, 다음 음식을 섭취한다.
    • 다음 음식이란, 아직 남은 음식 중 다음으로 섭취해야 할 가장 가까운 번호의 음식을 말한다.
  • 회전판이 다음 음식을 무지 앞으로 가져오는데 걸리는 시간은 없다고 가정한다.

무지가 먹방을 시작한 지 K 초 후에 네트워크 장애로 인해 방송이 잠시 중단되었다.무지는 네트워크 정상화 후 다시 방송을 이어갈 때, 몇 번 음식부터 섭취해야 하는지를 알고자 한다.각 음식을 모두 먹는데 필요한 시간이 담겨있는 배열 food_times, 네트워크 장애가 발생한 시간 K 초가 매개변수로 주어질 때 몇 번 음식부터 다시 섭취하면 되는지 return 하도록 solution 함수를 완성하라.

제한사항

  • food_times 는 각 음식을 모두 먹는데 필요한 시간이 음식의 번호 순서대로 들어있는 배열이다.
  • k 는 방송이 중단된 시간을 나타낸다.
  • 만약 더 섭취해야 할 음식이 없다면 1을 반환하면 된다.

정확성 테스트 제한 사항

  • food_times 의 길이는 1 이상 2,000 이하이다.
  • food_times 의 원소는 1 이상 1,000 이하의 자연수이다.
  • k는 1 이상 2,000,000 이하의 자연수이다.

효율성 테스트 제한 사항

  • food_times 의 길이는 1 이상 200,000 이하이다.
  • food_times 의 원소는 1 이상 100,000,000 이하의 자연수이다.
  • k는 1 이상 2 x 10^13 이하의 자연수이다.

입출력 예

food_times k result
[3, 1, 2] 5 1

입출력 예 설명

입출력 예 #1

  • 0~1초 동안에 1번 음식을 섭취한다. 남은 시간은 [2,1,2] 이다.
  • 1~2초 동안 2번 음식을 섭취한다. 남은 시간은 [2,0,2] 이다.
  • 2~3초 동안 3번 음식을 섭취한다. 남은 시간은 [2,0,1] 이다.
  • 3~4초 동안 1번 음식을 섭취한다. 남은 시간은 [1,0,1] 이다.
  • 4~5초 동안 (2번 음식은 다 먹었으므로) 3번 음식을 섭취한다. 남은 시간은 [1,0,0] 이다.
  • 5초에서 네트워크 장애가 발생했다. 1번 음식을 섭취해야 할 때 중단되었으므로, 장애 복구 후에 1번 음식부터 다시 먹기 시작하면 된다

풀이

나의 코드

def solution(food_times, k):
    idx = 0
    n = len(food_times)
    # 카운트 k가 0보다 작거나 같으면 종료
    for _ in range(k):
        food_times[idx] -= 1
        if not any(food_times):
            return -1
        idx = (idx + 1) % n
        # 비어있으면 다음 인덱스로
        while food_times[idx] <= 0:
            idx = (idx + 1) % n
    return idx+1

하나씩 돌면서 음식리스트 원소를 -1 하려고 했다. 정확도 검사는 다 맞았는데, 효율성 검사에서 하나도 맞지 못했다.. 계속 수정해봐도 통과되질 않아 책을 참고해보니 우선순위 큐(최소 heap)을 이용했었다.

시간이 적게 걸리는 음식부터 확인하는 탐욕적 접근 방법으로 해결할 수 있다. 모든 음식을 시간을 기준으로 정렬한 뒤에, 시간이 적게 걸리는 음식부터 제거해 나가는 방식을 이용하면 된다.

food_time = [8, 6, 4], k=15 라고 하면 가장 적게 걸리는 음식부터 제거하는 것이다.

세번째 음식이 4초로 가장 적게 걸리므로 (남은 음식의 개수) x (3번 음식을 먹는 시간) = 3 x 4 = 12초를 제거한다.

결과는 k = 3, 남은 음식 2개 [8-4, 6-4, 0] = [4, 2, 0] 이다.

그 다음 두 번째 음식이 2초로 적게 걸린다.

(남은 음식의 개수) x (2번 음식을 먹는 시간) = 2 x 2 = 4초, 남은 시간 k=3보다 크므로 빼지 않는다.

다음으로 먹어야 할 음식의 번호를 찾아 출력한다. ⇒ 일렬로 나열해보자 ⇒ (1번, 4), (2번, 2), (1번, 3), (2번, 1), (1번, 2), (1번, 1) ⇒ 3초 남았으므로 4번째 음식의 번호를 출력하면 정답이다.

책의 코드 참고하여 다시 작성

import heapq

def solution(food_times, k):
    # 전체 음식을 먹는 시간보다 k가 크거나 같다면 -1
    if sum(food_times) <= k:
        return -1
    
    # 시간이 작은 음식부터 빼야 하므로 우선순위 큐를 이용
    q = []
    for i in range(len(food_times)):
        # (음식 시간, 음식 번호) 형태로 우선순위 큐에 삽입
        heapq.heappush(q, (food_times[i], i+1))
    # 먹기 위해 사용한 시간
    sum_value = 0
    # 직전에 다 먹은 시간
    previous = 0

    # 남은 음식의 개수
    length = len(food_times)

    # (먹기 위해 사용한 시간) + (현재의 음식 시간 - 이전 음식 시간) * 현재 음식 개수와 k 비교
    # k보다 적은 시간일 때
    while sum_value + ((q[0][0] - previous) * length) <= k:
        now = heapq.heappop(q)[0] # 새롭게 가장 적은 시간을 가진 음식 꺼내기
        sum_value += (now - previous) * length # 지금까지 먹는데 사용한 시간
        length -= 1 # 다 먹은 음식 제외
        previous = now # previous에 가장 적은 시간을 가진 음식 담기

    # 남은 음식 중에서 몇 번째 음식인지 확인하여 출력
    result = sorted(q, key=lambda x:x[1]) # 음식 번호 기준으로 정렬
    return result[(k-sum_value)%length][1]

💡사고 과정 흐름공식화하자

  • 힙(Heap)
    • 완전 이진 트리의 일종으로 우선순위 큐를 구현하기 위해 사용하는 자료구조이다.
    • 여러 개의 값들 중 최댓값이나 최솟값을 빠르게 찾아내도록 만들어진 자료구조이다.
    • 힙은 일종의 반정렬 상태(느슨한 정렬 상태)를 유지한다.

댓글남기기