알고리즘 문제풀이/파이썬

[이코테-그리디] 기출 - 06.무지의 먹방 라이브

Ella_K 2022. 5. 9. 00:35

 

[문제]

https://programmers.co.kr/learn/courses/30/lessons/42891

 

코딩테스트 연습 - 무지의 먹방 라이브

 

programmers.co.kr

 


 

[정확성 통과, 효율성 실패 풀이] - 본인 풀이

🔎 아이디어

  • 가장 쉽게 떠오를 수 있는 방법은 문제의 입출력 예시를 코드로 구현하는 것이다. 

 

🔎 코드 설계

  • K초가 모든 음식을 먹는데 필요한 시간보다 크거나 같으면 애초에 K초 동안 모든 음식을 먹을 수 있다. 따라서 이를 확인해 조건이 만족되면 바로 -1을 반환한다.
  • K초가 점점 줄어들면서 시간이 남아 있는 음식이면 먹고, 음식의 남은 시간을 변경한다.
  • 그리고 다음으로 먹을 음식 번호를 찾는다.
음식 번호를 찾을 때 남은 시간이 0이 아닌 번호를 찾는다.다음 음식 번호는 (현재 음식 번호 + 1) 인데, (현재 음식 번호 + 1)이 음식 수를 초과할 수 있다.
따라서 (현재 음식 번호 + 1)를 음식 수로 나눈 나머지가 다음 음식 번호이다. 
  • K초가 0이될 때까지 위 과정을 반복한다.   
def idx(food_times, food_num, food_idx):
    # 남은 시간이 0이 아닌 음식 번호를 찾을 때까지 반복
    while True:
        # 다음 음식 번호는 (현재 음식 번호 + 1) 
        # 그런데 (현재 음식 번호 + 1)이 음식 수를 초과할 경우도 있다
        # 따라서 음식 수로 나눈 나머지가 다음 음식 번호
        food_idx = (food_idx + 1) % food_num
        if food_times[food_idx] != 0:
            return food_idx

def solution(food_times, k):
    food_idx = 0
    time = k 
    food_num = len(food_times)
    food_time_sum = sum(food_times)

    # k초 동안 모든 음식을 충분히 먹을 수 있다.
    if k >= food_time_sum:
        return -1
    
    while(time): # time이 줄어들면서 음식 먹음 
      if food_times[food_idx] != 0:
            # 시간이 남아 있는 음식이면 먹음. 시간 줄어듦    
            time -=1
            food_times[food_idx] -= 1
      # 다음으로 먹을 음식 번호 찾기
      food_idx = idx(food_times, food_num, food_idx)

    # 코드에서는 인덱스 번호를 0부터 설정했으므로 1을 더한다
    return food_idx + 1

 

🔎 단점

  • 정확성 테스트 제한사항에서 K는 1이상 2,000,000 이하의 자연수 이지만, 효율성 테스트 제한사항에서 K가 1이상 2 × 10^13 이하의 자연수이다.
  • 위의 코드는 시간복잡도가 적어도 O(K)이므로 효율성 테스트에서 실패하게 된다.
  • 시간 효율성을 통과하기 위해서 K초를 한 번에 줄이는 아래 방법을 사용한다. 

 


 

[성공 풀이] - 유튜브 ezsw님 풀이

🔎 아이디어

https://www.youtube.com/watch?v=zpz8SMzwiHM  ezsw님의 풀이 영상을 참고했다.

  • 음식을 먹는데 걸리는 시간 순으로 정렬해서 시간이 적게 걸리는 음식부터 다먹는 것을 고려한다.
  • 그 음식을 다먹었을 때 걸리는 시간을 구해 K에서 뺌으로써 K시간을 확 줄여나간다.
  • 다먹는데 걸리는 시간이 현재 남은 시간 K보다 클 경우, 남은 음식들을 번호 순으로 다시 정렬하고, 남은 시간 K에 따른 최종 결과를 구한다.
  • food_times = [3, 5, 1, 6, 5, 3], k=20 인 예를 들어보자

 

1. 아래 표는 음식 번호에 따른 음식을 먹는데 걸리는 시간을 나타낸다. 왼쪽의 food_times를 오른쪽과 같이 오름차순 정렬한다.

 

2. 3번 음식을 다 먹고 돌아오는데 걸리는 시간은 그림과 같이 1 × 6 = 6 이다. 따라서 남은 시간 K = 20 - 6 = 14 이다. 

3. 1번 음식을 다먹을려면 남은 음식들 만큼 두 바퀴를 돌면된다. 즉 1번 음식을 다먹는데 걸리는 시간은 아래 그림과 같이 2 (1번 시간 - 3번 시간)  × 5 (남은 음식 수) = 10 이다.  남은 시간 K = 14 - 10 = 4 가 된다.

 

4. 6번은 1번을 다 먹는동안 다 먹게 된다. 그래서 6번을 다 먹는 시간은 0이다. 남은 시간 K는 그대로 4이다.

5. 2번 음식을 다먹는데 걸리는 시간은 아래 그림과 같이 2 (2번 시간 - 6번 시간)  × 3 (남은 음식 수) = 6 이다. 그런데 남은 시간 K는 4이므로 6번을 다 먹을 수 없다.

6. '음식을 다 먹는데 걸리는 시간 > 남은 시간' 이 되는 경우에는 남은 음식을 번호 순으로 다시 정렬 한다.  2 -> 4 -> 5 순이 된다.  남은 시간 K는 4이므로 방송이 정상화 된 후 섭취해야하는 음식은 4번이된다. (2->4->5->2->4)

 

🔎 코드 

# 최악의 경우 음식의 수만큼만 roop를 돌면 되므로 시간복잡도가 줄어든다.
from operator import itemgetter

def solution(food_times, k):
  foods = [] # (먹는데 걸리는 시간, 음식 번호) 저장
  n = len(food_times) # 음식의 수
  for i in range(n):
    foods.append((food_times[i], i+1))
    
  # 튜플의 첫번째 인덱스 기준으로 정렬(걸리는 시간이 작은 음식부터 큰 음식순으로 정렬)
  foods.sort() 
  pretime = 0
  for i, food in enumerate(foods): # 음식을 먹는 roop
    diff = food[0] - pretime
    if diff != 0:
      spend = diff * n # 현재 음식을 다먹는데 걸리는 시간
      if spend <= k:
        k -= spend 
        pretime = food[0]
      else:
        idx = k % n # 여기서 n은 남은 음식의 수이다
        # 남은 음식을 번호 순으로 다시 정렬
        sublist = sorted(foods[i:], key=itemgetter(1)) 
        return sublist[idx][1]

    n -= 1 # 현재 음식 다 먹음
  # for문 도중 return이 안됬다는 것은 k가 남았다 
  # -> 음식을 다 먹었다 -> -1 반환  
  return -1