본문 바로가기

Algorithm/Programmers

Level 2: 광물 캐기, 그리디 알고리즘

Step 1: 피로도 계산

어떤 곡갱이를 사용해 광물을 캘 때는 미리 정해진 피로도를 소모합니다.

피로도는 이차원 배열로 미리 구성하면 인덱스를 사용하여 쉽게 접근 할 수 있습니다.

java
닫기
int[][] stamina = new int[][] {{1, 1, 1 }, { 5, 1, 1 }, { 25, 5, 1 }}; List<String> staminaIdx = Arrays.stream(new String[] { "diamond", "iron", "stone" }).toList();

예를 들어 인자 minerals{ "iron", "diamond", "iron" }인 경우 첫 번째 요소인 iron을 캐낼 때 곡갱이 별로 소모되는 피로도는 다음처럼 구할 수 있습니다.

java
닫기
int staminaIdxOf = staminaIdx.indexOf(minerals[0]); for (int i = 0; i < 3; ++i) { int v = stamina[i][staminaIdxOf]; }

Step 2-1: 광물을 Fragment로 구성

어떤 곡갱이를 선택하더라도 연속되는 광물 5개는 반드시 동일한 곡갱이로 캐내야 합니다.

광물 5개는 하나의 조각(Fragment)로 구성 할 수 있으며 Fragment는 3개의 곡갱이로 캐낼 때의 피로도 미리 계산 할 수 있습니다.

java
닫기
List<Integer[]> fragments = new ArrayList<>(); for (int i = 0; i < minerals.length; ++i) { int fragmentId = i / 5; if (fragmentId >= fragments.size()) ‌‌fragments.add(new Integer[] { 0, 0, 0 }); int staminaIdxOf = staminaIdx.indexOf(minerals[i]); for (int j = 0; j < 3; ++j) { ‌‌fragments.get(fragmentId)[j] += stamina[j][staminaIdxOf]; } }

문제에서 제시하는 입력#1은 두 개의 Fragment로 구성됩니다.

["diamond", "diamond", "diamond", "iron", "iron", "diamond", "iron", "stone"]
fragments[0]
["diamond", "diamond", "diamond", "iron", "iron"]
fragments[1]
["diamond", "iron", "stone"]
Input #1 sample
Fragment #1 sample
Fragment #2 sample
5 = 1+1+1+1+1
17 = 5+5+5+1+1
85 = 25+25+25+5+5
3 = 1+1+1
7 = 5+1+1
31 = 25+5+1
List<Integer[]> fragments

Step 2-2: Fragment 구성의 예외적인 중단

인자 picks는 곡갱이 3종류의 수량을 표현합니다. 곡갱이 1개는 광물을 5개 캐낼 수 있으며 더 이상 남아있는 곡갱이가 없으면 Fragment 구성도 중단합니다.

Step 2-1의 Fragment 구성 코드의 반복문에서 잔여 곡갱이 개수를 검사합니다.

java
닫기
int remainPicks = Arrays.stream(picks).sum() * 5; for (int i = 0; i < minerals.length && 0 < remainPicks; ++i, --remainPicks) { int fragmentId = i / 5; ​​​​... }

Step 3: 그리드 알고리즘을 사용하여 최적의 수 계산

이 문제를 푸는 방법은 크게 두 가지입니다.

  • 모든 곡갱이 조합을 사용하는 완전 탐색 풀이
  • 그리드 알고리즘을 사용하여 풀이

완전 탐색을 사용하게 되면 로직도 복잡해지고 가장 큰 문제는 시간 초과 오류가 발생합니다.

다음 코드에서는 그리드 알고리즘을 사용해서 문제를 해결합니다.

java
닫기
import java.util.*; class Solution { public int solution(int[] picks, String[] minerals) { ‌‌int[][] stamina = new int[][] {{1, 1, 1 }, { 5, 1, 1 }, { 25, 5, 1 }}; ‌‌List<String> staminaIdx = Arrays.stream(new String[] { "diamond", "iron", "stone" }).toList(); ‌‌ ‌‌List<Integer[]> fragments = new ArrayList<>(); ‌‌int remainPicks = Arrays.stream(picks).sum() * 5; ‌‌for (int i = 0; i < minerals.length && 0 < remainPicks; ++i, --remainPicks) ‌‌{ ‌‌‌int fragmentId = i / 5; ‌‌‌if (fragmentId >= fragments.size()) ‌‌‌‌fragments.add(new Integer[] { 0, 0, 0 }); ‌‌‌int staminaIdxOf = staminaIdx.indexOf(minerals[i]); ‌‌‌for (int j = 0; j < 3; ++j) { ‌‌‌‌fragments.get(fragmentId)[j] += stamina[j][staminaIdxOf]; ‌‌‌} ‌‌} ‌‌ ‌‌Collections.sort(fragments, (lp, rp) -> { ‌‌‌return rp[2] - lp[2]; ‌‌}); ‌‌ ‌‌int answer = 0; ‌‌for (Integer[] o : fragments) ‌‌{ ‌‌‌for (int i = 0; i < picks.length; ++i) { ‌‌‌‌if (0 < picks[i]) { ‌‌‌‌‌answer += o[i]; ‌‌‌‌‌--picks[i]; ‌‌‌‌‌break; ‌‌‌‌} ‌‌‌} ‌‌} ‌‌ ‌‌return answer; } }
코드 비고
Line 22:24 Collections.sort() Fragment를 피로도가 높은 순서로 내림차순 정렬합니다.
이때 편차가 가장 큰 stone 곡갱이를 사용한 경우로 계산합니다.
Line 27:36 for (int i = 0; i < picks.length; ++i) { } Fragment를 하나 처리 할 때 diamond 곡갱이부터 사용합니다.
만약 잔여 곡갱이가 없으면 다음 유형의 곡갱이를 사용합니다.
더보기

피로도가 높은 Fragment 순서대로 최적의 곡갱이를 선택하는 것이 중요합니다.