Algorithms
수정·

[Programmers] Lv3 주사위 고르기

by Dohoon Kim · 24년 06월 19일 02:05:50

주사위 고르기

문제 링크

문제 해설

이 문제는 Lv3 급 문제에서 몇년 전부터 자주 보이기 시작한 두개 이상의 알고리즘이 사용되는 문제 유형 중 하나이다. 조합과 이분 탐색이 사용된 문제로 이분 탐색은 효율성을 위해 사용한다.

문제의 조건을 살펴보면 아래와 같다

1. n 은 2의 배수이다.
2. 6개의 면에 숫자가 적힌 n개의 주사위가 주어진다.
3. 두 명의 플레이어 A, B는 각 n/2 개의 주사위를 가져간다.
4. 각 플레이어는 가져간 주사위를 굴려 나온 숫자들의 합을 상대방과 비교하여 승/무/패를 가린다.
5. 이 조건에 따라 가장 많은 승리할 수 있는 주사위 조합을 선택하라.

문제가 길게 적혀있지만 핵심은 위 다섯 가지로 요약 가능하다. 무승부를 제외한 승률을 구하는 문제가 아니기 때문에 승률이 아닌 승리하는 경우의 수만 구하면 문제를 해결 할 수 있다.

해결 순서

  1. DFS를 이용하여 주사위를 고르는 모든 조합의 수를 구하는 함수를 작성한다.
  2. DFS를 이용하여 주어진 주사위 조합으로 각 플레이어가 낼 수 있는 모든 점수의 조합을 구하는 함수를 작성한다.
  3. 플레이어 A와 플레이어 B의 점수를 2에서 구한 함수로 모두 구한다.
  4. 플레이어 B의 점수 조합(BScore) 을 오름차순 정렬한다.
  5. 플레이어 A의 점수 조합 중 하나를 선택하여 BScore에 이분 탐색을 진행, 승리 수를 반환한다.
  6. 플레이어 A의 모든 점수에 대해 5를 반복하여 총 승리 수를 구한다.
  7. 5와 6을 A의 모든 주사위 조합에 대해 진행한다.
  8. 최대 승리 수에 해당하는 인덱스를 반환한다.

Code

import java.util.*;

class Solution {

    private int[][] dice;
    private int[] comb;
    private int limit;
    private int[] answer;
    private List<int[]> diceCombs = new ArrayList<>();

    private int countWins(int target, List<Integer> scores) {
        int left = 0;
        int right = scores.size() - 1;
        int mid = 0;

        while(left <= right) {
            mid = (left + right) / 2;
            int value = scores.get(mid);

            if(value < target) { // 현재 값이 검색하려는 값보다 작다면
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return left;
    }

    private void makeScoreCombination(int[] indices, int start, int limit, int score, List<Integer> output) {
        if(start == limit) {
            output.add(score);
            return;
        }

        for(int i = 0 ; i < 6 ; i++) {
            makeScoreCombination(indices, start + 1, limit, score + dice[indices[start]][i], output);
        }
    }

    private void makeCombination(int start, int cur, int limit) {
        if(cur == limit) {
            diceCombs.add(Arrays.stream(comb).toArray());
            return;
        }

        for(int i = start ; i < dice.length ; i++) {
            comb[cur] = i;
            makeCombination(i + 1, cur + 1, limit);
        }
    }

    public int[] solution(int[][] dice) {
        this.dice = dice;
        limit = dice.length;
        answer = new int[limit / 2];
        comb = new int[limit / 2];
        makeCombination(0, 0, limit/2);

        int maxWin = 0;

        // System.out.println("Score result ");
        for(int i = 0 ; i < diceCombs.size() / 2 ; i++) {

            int[] aDices = diceCombs.get(i);
            int[] bDices = diceCombs.get(diceCombs.size() - i - 1);

            List<Integer> aScore = new ArrayList<>();
            List<Integer> bScore = new ArrayList<>();

            makeScoreCombination(aDices, 0, limit/2, 0, aScore);
            makeScoreCombination(bDices, 0, limit/2, 0, bScore);

            Collections.sort(aScore);
            Collections.sort(bScore);

            int aTotalWin = 0; // 승률은 중요하지 않다. 어차피 모두 같은 시행 회수이므로 승리 수만 합계하여 비교하면 된다.
            int bTotalWin = 0; // 승률은 중요하지 않다. 어차피 모두 같은 시행 회수이므로 승리 수만 합계하여 비교하면 된다.
            
            for(int aValue : aScore) {
                int winCount = countWins(aValue, bScore);
                aTotalWin+=winCount;
            }
            
            for(int bValue : bScore) {
                int winCount = countWins(bValue, aScore);
                bTotalWin += winCount;
            }
            

            if(aTotalWin > maxWin) {
                maxWin = aTotalWin;
                for(int dIdx = 0 ; dIdx < limit/2 ; dIdx++) {
                    answer[dIdx] = aDices[dIdx] + 1;
                }
            }
            
            if(bTotalWin > maxWin) {
                maxWin = bTotalWin;
                for(int dIdx = 0 ; dIdx < limit/2 ; dIdx++) {
                    answer[dIdx] = bDices[dIdx] + 1;
                }
            }
        }

        return answer;
    }
}

주의점

이분 탐색 문제에서 항상 주의해야하는 탐색 범위의 축소 방법과, 탐색 종료 조건을 신경 써야한다. 이 문제는 Lower Bound 를 구하는 문제 유형이기 때문에 위 코드와 같이 종료 조건을 지정해야 한다.