문제 ✨
N개의 여러 단위 동전이 주어질 때 거스름돈 M을 거슬러 줄 동전의 최소 개수를 구해라
Cf. 첫 번째 줄에는 동전 종류 수 N(1 <= N <= 50)
, 두 번째 줄에는 N개의 동전 종류, 마지막 줄에는 거슬러줄 금액 M(1 <= M <= 500)
가 입력된다.
풀이 ✨
DFS로도 풀 수 있지만, N값 범위가 커지면 1초 Time Limit에 걸려 DP(Dynamic Programming)으로 풀어야 한다. 처음에는 아주 단순한 방법으로 가능할 것으로 생각했다. 입력받은 수들을 내림차순으로 정렬해 루프를 돌고, 주어진 수 total
를 해당 순서의 수로 나눈 몫이 해당 동전의 수가 되고, total
값을 해당 순서의 수로 나눈 나머지값이 되게 하면 될 것이라 생각했다. 하지만 예외의 경우가 있음을 확인하면서 전제가 잘못되었다는 생각을 하게 되었다.
예를 들어, total
값이 129이고, 동전의 종류가 1, 8, 20, 25, 50
인 경우를 보자. 내가 처음에 생각했던 방식대로라면 129는 50 * 2 + 25 * 1 + 20 * 0 + 8 * 0 + 1 * 4
가 될 수 있다. 그러면 동전의 개수는 7개가 된다. 하지만 이 결과가 아닌 50 * 2 + 25 * 0 + 20 * 1 + 8 * 1 + 1 * 1
의 경우에도 나눠떨어지며, 이 때는 동전의 개수가 5개로 이전에 구했던 개수보다 작다. 좀 더 다이나믹하게 생각해볼 필요가 있는 것 같다.
result
라는 배열을 만들되, 이 배열의 크기를 total
으로 만든다. 이 배열의 i번째 인덱스의 값은 i라는 금액을 만드는데 필요한 최소 동전 개수라고 한다. 최종적으로는 result[total]
의 값을 출력하면 되는 것이다.
배열 result
의 모든 요소 값을 Integer.MAX_VALUE
로 두고, result[0]
의 값으로는 0을 부여한다. 그리고는 동전의 종류 만큼 반복을 수행한다. 이 때 이 반복(루프) 내부에서 result 의 요소들의 값이 변경될 수 있을 때마다 더 작은 값이 해당 인덱스의 값이 될 수 있게 한다.
동전의 종류로 만든 배열인 coin
이 존재한다. 그렇다면 아래와 같이 반복문이 돌게 될 것이다. 그렇다면 i가 1일 때는 배열 요소들(의 값)이 곧 인덱스가 될 것이다.
int[] coin = new int[n + 1];
// coin 요소 입력받는 과정 생략
for (int i = 1; i <= n; i++) {
... // 각 동전만으로 해당 값(인덱스)를 만드는 데에 필요한 동전의 수 구하기
}
i
가 2가 되면 어떨까? 2이하의 인덱스는 반복하지 않아도 된다. 어차피 2원짜리 동전으로는 해당 금액을 만들 수 없기 때문이다. (0개가 들 수는 있지만 결국 음수의 동전이 있지 않는 이상 해당 금액을 만들 수 있는 것은 아니기 때문에 ‘0이 최솟값이니 바꿔야 한다’고 생각하지 않는다.)
i
가 2일때를 생각해보자. j
라는 금액을 만들기 위한 동전의 개수를 구하기 위해서는 result[j]
를 구해야 하는데, 이를 result[j - coin[i]] + 1
이라고 바꿔보겠다. 이 의미는 j
원을 만들기 위한 동전 개수는 곧, (j - 동전 금액)을 만드는 동전 개수 + 1과 같다. 무슨 말인고 하니, 뒤에 1을 더해주도록 하고 만들어야 하는 금액을 해당 동전 하나 금액 만큼 뺀 값이다. 이렇게 접근하는 것은 이미 값을 구한 인덱스들의 값을 사용할 수 있기 때문이다.
i
가 2라면 반복문은 2부터 시작하되, 이 2라는 금액을 2원짜리 동전으로 만들기 위해서필요한 동전 수는 1이다. 이는 ‘0이라는 금액을 2원짜리 0개로 만드는데 드는 동전 수’에서 1 더한 값과 같다. 즉, 앞서 정의한 반복문 내부에서 돌아야 하는 반복문을 아래와 같이 구체화 될 수 있다. (내가 알고리즘을 이해한 방식)
이러한 방식으로 반복문을 다 돌고나면 입력받은 total
금액을 만들기 위한 동전의 최소 개수인 result[total]
을 출력할 수 있을 것이다.
for (int i = 1; i <= n; i++) {
for (int j = list[i]; j <= total; j++) {
result[j] = Math.min(result[j], result[j - list[i]] + 1);
}
}
1차 작성 코드(실패) ✍️
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Integer[] list = new Integer[n];
int[] cnt = new int[n];
for (int i = 0; i < n; i++) list[i] = sc.nextInt();
int total = sc.nextInt();
Arrays.sort(list, Collections.reverseOrder());
int temp = total;
for (int i = 0; i < n; i++) {
cnt[i] = temp / list[i];
temp = temp % list[i];
if (temp == 0) break;
}
System.out.println(Arrays.stream(cnt).sum());
}
}
2차 작성 코드 ✍️
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Integer[] list = new Integer[n];
for (int i = 0; i < n; i++) list[i] = sc.nextInt();
int total = sc.nextInt();
int[] result = new int[total + 1];
Arrays.fill(result, Integer.MAX_VALUE);
result[0] = 0;
for (int i = 0; i < n; i++) {
for (int j = list[i]; j <= total; j++) {
result[j] = Math.min(result[j], result[j - list[i]] + 1);
}
}
System.out.println(result[total]);
}
}
'자료구조 및 알고리즘' 카테고리의 다른 글
알고리즘) 가장 많이 받은 선물(프로그래머스, Graph와 인접 행렬 활용, Java) (3) | 2024.07.25 |
---|---|
알고리즘) 최대점수 구하기(냅색 알고리즘) (0) | 2022.09.01 |
알고리즘) 토마토(백준 7576번, 트리 탐색 BFS) (0) | 2022.08.22 |
알고리즘) 다익스트라 알고리즘(가중치 방향 그래프) (0) | 2022.08.22 |
알고리즘) 우선순위 큐 개념, 최대 수입 스케쥴(그리디 알고리즘, 우선순위 큐) (0) | 2022.08.22 |