algorithm/이코테

Chapter3. 그리디

study-minjeong 2024. 7. 12. 13:22

1. 개념

현재 상황에서 지금 가장 좋은 것만 고르는 방법
매 순간 가장 좋아보이는 것을 선택하며, 현재의 선택이 나중에 미칠 영향에 대해 고려하지 않음
‘가장 큰 순서대로’, ‘가장 작은 순서대로’와 같은 기준이 있음

 

예제 3-1) 거스름돈

문제

거스름돈으로 사용할 500원, 100원, 50원, 10원짜리 동전이 무한히 존재한다.
손님에게 거슬러 줘야 할 돈이 N원일 때 거슬러 줘야 할 동전의 최소 개수를 구하라.

풀이

큰 순서로 거슬러 준다.
거스름돈 배열에 큰 순서대로 선언을 해두고, 현재 금액(N)에서 거슬러줄 수 있는 만큼 거슬러 주고, 남은 금액은 그 다음 거스름돈으로 거슬러준다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Greedy1 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bufferedReader.readLine());
        int[] money = {500, 100, 50, 10};

        int result = 0;
        for (int i = 0; i < money.length; i++) {
            result += N/money[i];
            N %= money[i];
        }
        System.out.println(result);
    }
}

정당성

위의 예제에서는 가지고 있는 동전에서 큰 단위가 항상 작은 단위의 배수이므로, 그리디로 해결할 수 있다. 하지만 500, 400, 100원과 같이 배수가 아닐 때는 사용할 수 없다. (ex. 800원인 경우 그리디에 따르면 500+100+100+100인데, 400+400이 최소가 되기 때문) 따라서 문제 풀이를 위한 최소한의 아이디어를 떠올리고 정당한지 검토할 수 있어야 답을 도출할 수 있다.


 

2. 실습

큰수의 법칙

문제

큰수의 법칙
다양한 수로 이루어진 배열이 있을 때 주어진 수들을 M번 더하여 가장 큰 수를 만드는 법칙 (단, 배열의 특정한 인덱스에 해당하는 수가 연속해서 K번을 초과하여 더해질 수 없는 것이 특징)
배열의 크기 N, 숫자가 더해지는 횟수 M, 그리고 K가 주어질 때 동빈이의 큰 수의 법칙에 따른 결과를 출력해보기

풀이

제일 큰 수를 K번, 두번째 큰수를 1번, 다시 제일 큰수를 K번, .. 이렇게 반복하면 가장 큰 수를 만들 수 있다.

교재보고 이해 -> 제일 큰 수를 더하는 횟수를 구한다. {M/(K+1)}*K + M%(K+1)

코드

import java.util.Arrays;
import java.util.Scanner;

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

        int N = sc.nextInt();
        int M = sc.nextInt();
        int K = sc.nextInt();

        int[] nums = new int[N];
        for (int i = 0; i < N; i++) {
            nums[i] = sc.nextInt();
        }

        Arrays.sort(nums);

        int result = 0;
        int offset = 0;
        while(M>0){
            int cnt = 0;
            int index = 0;
            if (offset % 2==0){
                index = N-1;
                if (M>=K){
                    cnt = K;
                } else{
                    cnt = (M%K);
                }
            } else{
                index = N-2;
                cnt = 1;
            }
            result += nums[index] * cnt;
            M -= cnt;
            offset += 1;
        }

        System.out.println(result);
    }
}
import java.util.Arrays;
import java.util.Scanner;

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

        int N = sc.nextInt();
        int M = sc.nextInt();
        int K = sc.nextInt();

        int[] nums = new int[N];
        for (int i = 0; i < N; i++) {
            nums[i] = sc.nextInt();
        }

        Arrays.sort(nums);

        int count = (M/(K+1))*K + M%(K+1);
        int first = nums[N-1] * count;
        int second = nums[N-2] * (M-count);
        int result = first + second;

        System.out.println(result);
    }
}

 

숫자 카드 게임

문제

가장 높은 숫자가 쓰인 카드를 뽑아야 한다. N*M 형태로 숫자카드가 놓여져있다. 선택한 행에서 가장 숫자가 낮은 카드를 뽑아야 한다. 최종적으로 가장 높은 숫자 카드를 뽑을 수 있도록 해야한다.
ex) 아래의 카드에서는 첫번째, 두번째 행을 선택하면 1을 뽑아야 하므로 세번째 행을 선택하여 2를 뽑는 것이 정답이다.
3 1 2
4 1 4
2 2 2

풀이

처음에는 입력받은 값을 배열에 저장하는 방법을 사용했다. 배열에 값을 오름차순으로 둬 인덱스가 0인 값을 리스트에 넣고 가장 큰 값 출력

한줄씩 입력 받는다면 굳이 배열에 저장할 필요는 없고, 변수에 저장해서 할 수 있다는 생각을 했다. -> 결과값을 저장해두는 변수를 두고, 각 행마다 가장 작은 값과 결과값을 비교하여 더 큰 값을 결과값에 저장

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Greedy3 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String string = bufferedReader.readLine();
        int N = Integer.parseInt(string.split(" ")[0]);
        int M = Integer.parseInt(string.split(" ")[1]);

        List<Integer> nums = new ArrayList<>();
        for (int i = 0; i<M; i++){
            String numString = bufferedReader.readLine();
            List<Integer> numList = new ArrayList<>();
            for (int j = 0; j<N; j++){
                numList.add(Integer.parseInt(numString.split(" ")[j]));
            }
            Collections.sort(numList);
            nums.add(numList.get(0));
        }
        nums.sort(Collections.reverseOrder());
        System.out.println(nums.get(0));
    }
}

 

1이 될 때까지

문제

  1. N에서 1을 뺀다
  2. N을 K로 나눈다
    위의 두 방법으로 1이 되는 최소 횟수를 구한다.

풀이

가장 많이 나눌 수 있을 때 최소 횟수가 될 것이라고 생각했다.
N이 K로 나누어지면 몫만큼 더하고, 아니면 1 더한 후 N에서 1을 뺀다.
(예외가 있나?)

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Greedy4 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String string = bufferedReader.readLine();
        int N = Integer.parseInt(string.split(" ")[0]);
        int K = Integer.parseInt(string.split(" ")[1]);

        int result = 0;
        while(true){
            if (N%K == 0){
                result += N/K;
                break;
            } else {
                N -= 1;
                result += 1;
            }
        }
        System.out.println(result);
    }
}