알고리즘 - 그리디

Greedy

Posted by Yan on March 1, 2022

Greedy 탐욕법

  • 현재 상황에서 지금 당장 좋은 것만 고르는 방법
  • 문제를 풀기 위한 최소한의 아이디어를 떠올릴 수 있는 능력을 요구한다.
  • 정당성 분석이 중요하다.
  • 단순히 가장 좋아보이는 것을 반복적으로 선택해도 최적의 해를 구할 수 있는지 검토한다.
  • 일반적인 상황에서 그리디 알고리즘은 최적의 해를 보장할 수 없을 때가 많다.
  • 그러나 대부분의 그리디 문제는 탐욕법으로 얻은 해가 최적의 해가 되는 상황에서 추론할 수 있어야 풀리도록 출제된다.

거스름돈 문제 예시

손님에게 거슬러 줘야 할 돈이 N원일 때, 거슬러 줘야 할 동전의 최소 개수 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
  public static void main(String[] args) {
    int n = 1260;
    int cnt = 0;
    int[] coinTypes = {500, 100, 50, 10};

    for (int i = 0; i < 4; i++) {
      cnt += n / coinTypes[i];
      n %= coinTypes[i];
    }

    System.out.println(cnt);
  }
}

큰 수의 법칙 문제 예시

  • input
    • 첫째 줄: N, M, K
    • 둘째 줄: 길이가 N인 배열
  • output : 배열에서 가장 큰 수를 연속해서 K번까지만 더할 수 있다는 조건 하에, M번까지의 수를 더했을 때 결과값

  • 해결방법: 반복되는 수열에 대해서 파악하기
    • 반복되는 수열의 길이 : K+1
    • 수열이 반복되는 횟수 : M / (K+1)
    • 가장 큰 수가 등장하는 횟수: M / (K+1) * 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
26
27
28
29
30
31
32
33
34
35
36
public static void main(String[] args) throws IOException {
  BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
  StringTokenizer st1 = new StringTokenizer(br.readLine());
  
  int n = Integer.parseInt(st1.nextToken());
  int m = Integer.parseInt(st1.nextToken());
  int k = Integer.parseInt(st1.nextToken());

  int[] arr = new int[n];

  StringTokenizer st2 = new StringTokenizer(br.readLine());
  for (int i = 0; i < n; i++) {
      arr[i] = Integer.parseInt(st2.nextToken());
  }

  // 최댓값 구하기 stream
  // int max1 = Arrays.stream(arr).max().getAsInt();
  // 궁금해서 찾아본 배열 역순 정렬 stream
  // int[] sortedArr = Arrays.stream(arr).boxed()
  //        .sorted(Collections.reverseOrder())
  //        .mapToInt(Integer::intValue)
  //        .toArray();
  // int max1 = sortedArr[0];
  // int max2 = sortedArr[1];

  Arrays.sort(arr);

  int max1 = arr[arr.length-1];
  int max2 = arr[arr.length-2];
  int result = 0;

  result += (m / (k+1) * k) * max1;
  result += (m - (m / (k+1) * k)) * max2;

  System.out.println(result);
}

숫자 카드 게임 문제 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  public static void main(String[] args) throws IOException {
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      StringTokenizer st1 = new StringTokenizer(br.readLine());
      int n = Integer.parseInt(st1.nextToken());
      int m = Integer.parseInt(st1.nextToken());
      int[] minInEachMArr = new int[n];
      StringTokenizer st2;
      for (int i = 0; i < n; i++) {
          st2 = new StringTokenizer(br.readLine());
          for (int j = 0; j < m; j++) {
              int nextNum = Integer.parseInt(st2.nextToken());
              if (j == 0) {
                  minInEachMArr[i] = nextNum;
              } else if (nextNum < minInEachMArr[i]){
                  minInEachMArr[i] = nextNum;
              }
          }
      }
      int maxInArr = Arrays.stream(minInEachMArr).max().getAsInt();
      System.out.println(maxInArr);
  }

1이 될 때까지 문제 예시

  • input
    • 어떤 수 N, K
  • output

    • (1) N에서 1을 뺀다 (2) N을 K로 나눈다 두 과정을 선택해서 수행했을 때 N이 1이 될 때까지의 수행 횟수 최솟값
  • 해결방법: 최대한 많이 나누기
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
26
27
28
29
30
31
import java.util.*;

public class Main {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.inputStream());

    // 공백을 기준으로 구분하여 입력 받기
    int n = sc.nextInt();
    int k = sc.nextInt();
    int result = 0;

    while(true) {
      // n이 k로 나누어지는 수가 될 때까지 빼기
      int target = (n / k) * k; // k로 나누어 떨어지는 가장 가까운 수를 찾는다.

      result += (n - target); // (1) 실행 횟수를 result에 더해준다.

      n = target;

      if (n < k) break; // 더 이상 나눌 수 없을 때 반복문을 탈출한다.

      result += 1; // (2) 실행 횟수를 result에 더해준다.

      n /= k;
    }

    // 마지막으로 남은 수에 대하여 1씩 빼기
    result += (n - 1) * k;
    System.out.println(result);
  }
}
reference

[이것이 코딩 테스트다 with Python] 12강 그리디 알고리즘 개요