

처음에는 문제를 읽고 브루트포스를 떠올리고 구현하려던 도중, 최악의 경우 시간초과가 날 수 있다는 사실을 알아챘다.
예를 들어 주사위가 10개인 경우 주사위를 5개 뽑는 경우 수는 10C5 (=252), 각 조합마다 승, 무, 패를 판단하는 경우의 수는 6^10으로 총 연산의 수는 6^10 x 10C5(252)로 약 150억의 연산 횟수가 나오게 된다.
모든 과정을 브루트포스를 이용해 문제를 풀지 않고 다른 방법도 병행하며 수행속도를 어떻게 빠르게 할 수 있을까 생각하던 도중, 각 조합마다 승, 패를 판단하는 경우의 수를 빠르게 구할 수 있는 방법을 떠올려 냈고, 각 조합의 승리 횟수를 기록해 이분탐색을 이용하는 방법을 생각했다.
모든 합 중 하나를 A, 그 합이 나오는 횟수를 B라고 하자.상대방이 가진 주사위를 굴렸을 때 나올 수 있는 모든 합(C)의 조합들 사이에서 이분 탐색을 진행하여 A를 기준으로 왼쪽에 A보다 작은 C들이 오도록 한다. (내가 이기는 경우를 판별)상대방이 가진 주사위를 굴렸을 때 C가 나오는 횟수(D) x B 의 합을 구해 변수에 저장한다. ArrayList<pair> resultListB = new ArrayList<>();
Iterator<Integer> keys = resultMapB.keySet().iterator();
while (keys.hasNext()) { // Map 순회
Integer key = keys.next();
resultListB.add(new pair(key, resultMapB.get(key)));
}
// 이분탐색을 위한 배열 정렬 -> Key값 기준 오름차순
Comparator<pair> cp = new Comparator<pair>() {
@Override
public int compare(pair o1, pair o2) {
return o1.a - o2.a;
}
};
resultListB.sort(cp);
import java.util.*;
class Solution {
int max = 0, tmpMax = 0;
static int[][] dice;
Stack<Integer> currentDiceList, answerDiceList = new Stack<Integer>();
Map<Integer, Integer> resultMap = new HashMap<>();
public int[] solution(int[][] dice) {
this.dice = dice;
selectDice(0, 0, new Stack<Integer>()); // dfs
int[] answer = new int[answerDiceList.size()];
for (int i = 0; i < answerDiceList.size(); i++) {
answer[i] = answerDiceList.get(i) + 1;
}
return answer;
}
public void selectDice(int depth, int index, Stack<Integer> diceList) { // 사용할 주사위의 조합 구하기
if (depth == dice.length/2) {
// selected
this.currentDiceList = diceList;
// 굴려서 나온 각 수의 합을 map으로 저장하기
resultMap.clear();
rollDice(0, 0, diceList);
Map<Integer, Integer> resultMapA = new HashMap<>(resultMap); // 깊은 복사
// 상대의 주사위도 결정
Stack<Integer> diceListB = new Stack<Integer>();
for (int i = 0; i < dice.length; i++) {
if (!diceList.contains(i)) diceListB.add(i);
}
resultMap.clear();
rollDice(0, 0, diceListB);
Map<Integer, Integer> resultMapB = new HashMap<>(resultMap);
calc(resultMapA, resultMapB);// binary search
return;
}
for (int i = index; i < dice.length; i++) {
diceList.push(i);
selectDice(depth + 1, i + 1, diceList);
diceList.pop();
}
}
public void rollDice(int depth, Integer sum, Stack<Integer> diceList) {
// 굴려서 나온 각 수의 합을 구해서 map으로 저장
// Key : 각 수의 합, Value : 해당 합이 나오는 총 횟수
if (depth == dice.length/2) {
if (resultMap.get(sum) == null)
resultMap.put(sum, 1);
else
// 위의 if문이 없다면 가장 처음의 호출에서 resultMap.get(sum)은 null을 반환하게 되므로 런타임 에러 발생
resultMap.put(sum, resultMap.get(sum) + 1);
return;
}
for (int i = 0; i < 6; i++) {
sum += dice[diceList.get(depth)][i];
rollDice(depth + 1, sum, diceList);
sum -= dice[diceList.get(depth)][i];
}
}
public void calc(Map<Integer, Integer> resultMapA, Map<Integer, Integer> resultMapB) {
// 인덱스를 활용한 비교를 위해 map을 lsit<pair<int, int>> 형태로 변환해준다.
class pair {
public Integer a, b;
pair(int a, int b) {
this.a = a;
this.b = b;
}
}
ArrayList<pair> resultListB = new ArrayList<>();
Iterator<Integer> keys = resultMapB.keySet().iterator();
while (keys.hasNext()) { // Map 순회
Integer key = keys.next();
resultListB.add(new pair(key, resultMapB.get(key)));
}
// 이분탐색을 위한 배열 정렬 -> Key값 기준 오름차순
Comparator<pair> cp = new Comparator<pair>() {
@Override
public int compare(pair o1, pair o2) {
return o1.a - o2.a;
}
};
resultListB.sort(cp);
// 이분 탐색을 활용해 이기는 횟수를 계산한다.
tmpMax = 0;
for (int i : resultMapA.keySet()) {
int left = 0, right = resultListB.size() - 1;
int mid = (left + right) / 2;
while (left <= right) {
mid = (left + right) / 2;
if (i > resultListB.get(mid).a)
left = mid + 1;
else if (i == resultListB.get(mid).a)
break;
else
right = mid - 1;
}
for (int j = 0; j < mid; j++) {
tmpMax += resultListB.get(j).b * resultMapA.get(i);
}
if(i > resultListB.get(mid).a) tmpMax += resultListB.get(mid).b * resultMapA.get(i);
}
if (max < tmpMax) {
max = tmpMax;
answerDiceList.clear();
for (int j : currentDiceList)
answerDiceList.push(j);
}
}
}
구현만 하면 되는 생각보다 간단한 문제라고 생각했는데, 시간복잡도도 고려해야 하고 조합과 이분탐색을 이용해야 했기에 쉽지 않은 문제였던 것 같다.
