알고리즘

[2024 KAKAO INTERN] 주사위 고르기

moongi 2025. 6. 5. 12:14
728x90
반응형

 

https://school.programmers.co.kr/learn/courses/30/lessons/258709

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

문제 유형

  • DP + 누적합
  • DFS + 이분 탐색

 

문제 난이도

  • Lev3

 

문제 분석

A와 B가 주사위를 N / 2 개씩 나눠갖고, 각 주사위를 모두 돌려, 나온 숫자의 합을 서로 비교해서 A가 승리할 수 있도록 가장 승률이 좋은 주사위 번호들을 출력하는 문제이다.

문제를 이해하는덴 어렵지 않았지만, 시간 초과를 해결하기 위해 고민하다 다른 사람들의 풀이를 참고한 문제이다.

우선 문제에서 핵심은
1. (N / 2)개의 주사위 고르기
2. 고른 주사위와 고르지 않은 주사위들간의 나올 수 있는 모든 합의 경우의 수를 비교해서 승리할 확률이 높은 주사위를 갱신하기

우선 1번은 n이 최대 10개라고 했을 때, 10C5 = 252개이므로, 시간 초과가 나지 않기 때문에 조합을 사용하였다.

2번은 선택한 주사위와 선택하지 않은 주사위를 각각, 합을 계산하기 위해 DFS를 활용해서 합들을 배열에 저장해주었다.
이렇게 되면 각각 6^5 = 7776가지인데, 이걸 단순하게 완전 탐색을 하게 되면 6^10 = 7776 * 7776 이고, 조합까지 곱하게 되면252 * 7776 * 7776 이므로 시간 초과가 날 것이다.

따라서 시간 복잡도를 줄이기 위해 선택하지 않은 주사위들을 정렬한 이후, 이분 탐색을 통해 개수들을 더해주었다. 
-> 사실 현재 부분도 시간 초과가 날 수 있지만, 프로그래머스의 JVM 성능 및 최적화 수행 등으로 문제에서는 시간 초과가 나지 않았지만, 빠르게 풀려면 DP + 누적합을 사용해야 한다.

 

 

코드 분석

 

조합을 통해 주사위를 선택하는 코드이다.

(N / 2) 개를 선택하면, 주사위들간의 합을 비교하고, 가장 승률이 좋은 주사위로 갱신한다.

현재 선택된 주사위 번호는 배열의 인덱스에 해당하므로 +1 해준다.

// 주사위 선택
static void pickDice(int cnt, int start, int[][] dice) {

    if(cnt == N / 2) {

        int winCount = calculateWin(dice);

        if(max < winCount) {
            max = winCount;

            for(int i = 0; i < selected.size(); i++) {
                result[i] = selected.get(i) + 1;
            }
        }

        return;
    }

    for(int i = start; i < N; i++) {

        selected.add(i);
        pickDice(cnt+1, i+1, dice);
        selected.remove(selected.size()-1);
    }

}

 

주사위들간의 합을 계산하는 코드이다. 우선 비교할 주사위를 정렬하고, 이분 탐색을 통해, A의 주사위 합을 target으로 더 작다면 작은 개수 만큼은 이기는 경우이므로, 카운팅 해준다.

static int calculateWin(int[][] dice) {
        
    int count = 0;

    makeArrAB(dice);

    // arrA, arrB에 대하여 승패를 결정한다.

    Collections.sort(arrB);

    // 승부를 결정할 때, 이분 탐색을 통하여 시간복잡도를 줄인다. O(6^5 * log 6^5)

    for(int i = 0; i < arrA.size(); i++) {

        int target = arrA.get(i);

        int st = 0;
        int et = arrB.size() - 1;

        int idx = -1;
        while(st <= et) {

            int mid = (st + et) >> 1;

            if(target <= arrB.get(mid)) {
                et = mid - 1;
            } else {
                idx = Math.max(idx, mid);
                st = mid + 1;
            }

        }
        if(idx != -1) count += idx + 1;

    }

    return count;
}

 

주사위들의 합을 저장하기 위해 사용하는 코드이다. 깊이 우선 탐색을 통해, 모든 경우의 합을 저장한다.

// 선택된 주사위들의 합 분포를 저장
static void sumDice(int cnt, int sum, int[][] dice, List<Integer> arr) {

    if(cnt == N / 2) {
        arr.add(sum);
        return;
    }

    for(int i = 0; i < 6; i++) {
        int newSum = sum + dice[cnt][i];
        sumDice(cnt+1, newSum, dice, arr);
    }   
}

 

선택된 주사위들이 갖는 주사위 면 번호를 저장하고, A, B 주사위에 대해 합 분포를 저장하기 위해 sumDice()를 호출한다.

static void makeArrAB(int[][] dice) {
        
    // 선택된 주사위들의 합 분포를 저장한 배열
    arrA = new ArrayList<>();
    arrB = new ArrayList<>();

    int[][] diceA = new int[N / 2][6];
    int[][] diceB = new int[N / 2][6];

    int a = 0, b = 0;
    for(int i = 0; i < N; i++) {

        if(selected.contains(i)) {
            diceA[a++] = dice[i];
        } else {
            diceB[b++] = dice[i];
        }

    }

    sumDice(0, 0, diceA, arrA);
    sumDice(0, 0, diceB, arrB);

}

 

전체 코드

import java.io.*;
import java.util.*;

class Solution {
    
    // 주사위 선택
    static void pickDice(int cnt, int start, int[][] dice) {
        
        if(cnt == N / 2) {
            
            int winCount = calculateWin(dice);
            
            if(max < winCount) {
                max = winCount;
                
                for(int i = 0; i < selected.size(); i++) {
                    result[i] = selected.get(i) + 1;
                }
            }
            
            return;
        }
        
        for(int i = start; i < N; i++) {
            
            selected.add(i);
            pickDice(cnt+1, i+1, dice);
            selected.remove(selected.size()-1);
        }
        
    }
    
    static int calculateWin(int[][] dice) {
        
        int count = 0;
        
        makeArrAB(dice);
        
        // arrA, arrB에 대하여 승패를 결정한다.
        
        Collections.sort(arrB);
        
        // 승부를 결정할 때, 이분 탐색을 통하여 시간복잡도를 줄인다. O(6^5 * log 6^5)
        
        for(int i = 0; i < arrA.size(); i++) {
            
            int target = arrA.get(i);
            
            int st = 0;
            int et = arrB.size() - 1;
            
            int idx = -1;
            while(st <= et) {
                
                int mid = (st + et) >> 1;
                
                if(target <= arrB.get(mid)) {
                    et = mid - 1;
                } else {
                    idx = Math.max(idx, mid);
                    st = mid + 1;
                }
                
            }
            if(idx != -1) count += idx + 1;
            
        }
        
        return count;
    }
    
    
    // 선택된 주사위들의 합 분포를 저장
    static void sumDice(int cnt, int sum, int[][] dice, List<Integer> arr) {
        
        if(cnt == N / 2) {
            arr.add(sum);
            return;
        }
        
        for(int i = 0; i < 6; i++) {
            int newSum = sum + dice[cnt][i];
            sumDice(cnt+1, newSum, dice, arr);
        }   
    }
    
    static void makeArrAB(int[][] dice) {
        
        // 선택된 주사위들의 합 분포를 저장한 배열
        arrA = new ArrayList<>();
        arrB = new ArrayList<>();
        
        int[][] diceA = new int[N / 2][6];
        int[][] diceB = new int[N / 2][6];
        
        int a = 0, b = 0;
        for(int i = 0; i < N; i++) {
            
            if(selected.contains(i)) {
                diceA[a++] = dice[i];
            } else {
                diceB[b++] = dice[i];
            }
            
        }
        
        sumDice(0, 0, diceA, arrA);
        sumDice(0, 0, diceB, arrB);
        
    }
    
    
    
    static int N, max = 0;
    static int[] result;
    static List<Integer> selected, arrA, arrB;
    public int[] solution(int[][] dice) {
        N = dice.length;
        result = new int[N / 2];
        
        selected = new ArrayList<>();
        
        pickDice(0, 0, dice);
        
        
        return result;
        
    }
}

 

728x90
반응형