[Algorithm] 그리디 알고리즘

Intro

“이것이 코딩테스트다”를 공부하면서 알고리즘을 하나씩 정리하고자 한다.

그리디 알고리즘

그리디 알고리즘이란, 탐욕법이라고도 불리며, 탐욕법이란 어떤 문제가 있을 때 현재 상황 에서 지금 당장 좋은 것만 고르는 방법을 뜻한다. 지금 당장 좋은 것만 고르기에 현재의 선택이 나중에 미칠 영향을 고려하지 않는다.

  • 그리디 알고리즘은 지금 당장 좋은 것만 고르기에 대부분 가장 큰 순서대로, 가장 작은 순서대로 같은 기준을 갖고 문제를 풀이하기에 정렬 알고리즘과 짝을 이뤄서 생각해야 한다.

예제1. 거스름돈 문제

당신은 음식점의 계산을 도와주는 점원이다. 카운터에는 거스름돈으로 사용할 500원, 100원, 50원, 10원짜리의 동전이 무한히 존재한다고 가정한다. 손님에게 거슬러줘야 할 돈이 N원일 때 거슬러 줘야 할 동전의 최소 개수를 구하라. 단, 거슬러 줘야 할 돈 N은 항상 10의 배수이다.

  • 해결 방법(아이디어)
    • 그리디 알고리즘으로 접근하면 문제에서 거슬러줘야 할 동전의 “최소 개수”를 구하라고 제시하였다.
    • 이는 가장 화폐단위가 큰 금액부터 나눠서 거슬러주면 거스름돈의 동전의 최소 개수를 구할 수 있다는 것을 알 수 있다.
  • 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      # 금액
      n = int(input())
      # 거스름돈 타입을 리스트로 선언
      coin_type = [500, 100, 50, 10]
      # 거스름돈 개수(변수)
      cnt = 0
      # 타입별로 나누고 몫은 개수로 증감하고 나머지는 n에 대입하여 반복문 진행
      for i in coin_type:
          # 코인 개수
          cnt += n // i
    
          # 나머지 금액
          n %= i
    
      print(cnt)
    

예제2. 큰 수의 법칙

동빈이의 큰수의 법칙은 다양한 수로 이루어진 배열이 있을 때 주어진 수들을 M번 더하여 가장 큰수를 만드는 방법이다. 단, 배열의 특정한 인덱스에 해당하는 수가 연속해서 K번을 초과하여 더해질 수 없는 것이 이 법칙의 특징이다.

예를 들어 순서대로 2, 4, 5, 4, 6으로 이루어진 배열이 있을 때, M이 8이고 K가 3이라고 가정하자. 이 경우 특정한 인덱스의 수가 연속해서 세 번까지만 더해질 수 있으므로 큰 수의 법칙에 따른 결과는 6 + 6 + 6 + 5 + 6 + 6 + 6 + 5인 46이 된다. 단, 서로 다른 인덱스에 해당하는 수가 같은 경우에도 서로 다른 것으로 간주한다.

예를 들어 순서대로 3, 4, 3, 4, 3으로 이루어진 배열이 있을 때 M이 7이고 K가 2라고 가정하자. 이 경우 두 번째 원소에 해당하는 4와 네 번째 원소에 해당하는 4를 번갈아 두 번씩 더하는 것이 가능하다. 결과적으로 4 + 4 + 4 + 4 + 4 + 4 + 4 인 28이 도출된다. 배열의 크기 N, 숫자가 더해지는 횟수 M, 그리고 K가 주어질 때 동빈이의 큰 수의 법칙에 따른 결과를 출력하시오.

  • 입력 조건
    • 첫째 줄에 N(2<=N<=1000), M(2<=M<=1000), K(1<=K<=10000)의 자연수가 주어진다. (공백으로 구분)
    • 둘째 줄에 N개의 자연수가 주어진다. 자연수는 1이상 10000이하의 수로 주어진다. (공백으로 구분)
  • 해결방법(아이디어)
    • 큰 수를 구하는 알고리즘으로, 입력한 자연수 중에서 가장 큰 값을 K번 연속으로 더하고, 두 번째로 큰 값을 한 번 더하는 것을 반복하게 되면 큰 수를 구할 수 있다.
  • 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    # 큰 수의 법칙 문제
      # 1. n(배열 크기), m(총 더해지는 횟수), k(연속 더할 수 있는 횟수)
      n,m,k = map(int,input().split())
      # 2. 자연수 입력(공백을 기준으로 분리하여 정수형으로 변환한 뒤 map 객체를 list로 변환.)
      numlist = list(map(int, input().split()))
      # 가장 큰 수, 두 번째 큰 수를 도출하기 위해 오름차순 정렬.
      numlist.sort()
        
      result = 0
      while m > 0:
          # k번만큼 반복하여 연속으로 더한다.
          for i in range(k):
              # 만약, m이 0이 되면 반복문을 종료.
              if m == 0:
                  break
              result += numlist[-1]
              # 연산할 때 마다 -1를 해줘서 연산 횟수를 -1 해준다.
              m -= 1
          if m == 0:
              break
          # 2번째로 큰 수는 1번만 더한다.
          result += numlist[-2]
          m -= 1
    
      print(result)
    

예제3. 1이 될 때까지

어떤수 N이 1이 될 때까지 다음의 두 과정 중 하나를 반복적으로 선택하여 수행하려고한다. 단, 두번째 연산은 N이 K로 나누어떨어질 때만 선택할 수 있다.
1. N에서 1을 뺀다.
2. N을 K로 나눈다.

예를 들어, N이 17, K가 4라고할 때 1번 과정을 수행하면 16이 되고, 이후 2번 과정을 2번 수행하면 N은 1이 된다. 결과적으로 전체 과정을 실행한 횟수는 3이 된다. N과 K가 주어졌을 때 N이 1이 될 때까지 1번 혹은 2번의 과정을 수행해야 하는 최소 횟수를 구하는 프로그램을 작성하시오.

  • 입력 조건
    • 첫째 줄에 N(2 <= N <= 100000)과 K(2 <= K <= 100000)가 공백으로 구분되며 각각 자연수로 주어진다.
  • 해결 방법(아이디어)
    • 문제에서 N이 1이 될 때까지의 “최소 횟수”를 구하라고 제시했다.
    • 제시한 연산 방법 중에서 1을 1이 될 때까지 뺴는 것 보다 K로 나눴을 때 연산 횟수가 가장 적다.
    • 그렇기에 K로 나눠질 때까지 -1 연산을 하고, K로 나누는 작업을 했을 때 최소횟수가 나온다.
  • 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      # 1. N, K 입력 받기
      N, K = map(int, input().split())
    
      cnt = 0
      # 2. 나눠지는 지 확인
      while N != 1:
          if N % K == 0:
              N = N // K  
              cnt += 1
          else:
              N = N - 1
              cnt += 1
    
      print(cnt)
    

참고 사이트