[프로그래머스 JAVA] 주사위 고르기
2024. 9. 14. 18:42ㆍ프로그래머스
https://school.programmers.co.kr/learn/courses/30/lessons/258709
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
문제 접근 방법
- 주사위 조합을 구한다. (n개의 주사위를 n/2개로 나눈다.)
- 주사위 조합에서 나올 수 있는 합의 경우를 구한다. (각 주사위의 눈금을 사용)
- 2개의 조합을 비교해서 승리 횟수를 센다.
풀이 방법 (3가지)
모든 조합을 백트래킹으로 하나씩 비교해서 승리 횟수를 누적
- 완전탐색
- 시간초과
코드
더보기
public class Solution {
static int length;
static int[][] diceS;
static boolean[] visited;
static int maxWin = 0;
static int[] answer;
public static int[] solution(int[][] dice) {
length = dice.length;
diceS = dice;
visited = new boolean[length];
answer = new int[length / 2];
selectDice(0, 0);
return answer;
}
private static void selectDice(int curr, int depth) {
if (depth == length / 2) {
int winCount = calcWin();
if (winCount > maxWin) {
maxWin = winCount;
int k = 0;
for (int j = 0; j < visited.length; j++) {
if (visited[j]) {
answer[k++] = j + 1;
}
}
}
return;
}
for (int i = curr; i < length; i++) {
visited[i] = true;
selectDice(i + 1, depth + 1);
visited[i] = false;
}
}
static int winCountS = 0;
private static int calcWin() {
winCountS = 0;
calcScores(0, 0, 0);
return winCountS;
}
private static void calcScores(int diceIdx, int sum1, int sum2) {
if (diceIdx == length) {
if (sum1 > sum2) winCountS++;
return;
}
for (int i = 0; i < 6; i++) {
if (visited[diceIdx]) {
calcScores(diceIdx + 1, sum1 + diceS[diceIdx][i], sum2);
} else {
calcScores(diceIdx + 1, sum1, sum2 + diceS[diceIdx][i]);
}
}
}
}
모든 조합을 List<주사위 점수 합>로 미리 계산,
비교 대상을 정렬 이후 이분탐색으로 승리 횟수를 누적
- 이분탐색
- 통과
결과, 코드
더보기


package test;
import java.util.*;
public class Solution {
static int length;
static int[][] diceS;
static boolean[] visited;
static int maxWin = 0;
static int[] answer;
public static int[] solution(int[][] dice) {
length = dice.length;
diceS = dice;
visited = new boolean[length];
answer = new int[length / 2];
selectDice(0, 0);
return answer;
}
private static void selectDice(int curr, int depth) {
if (depth == length / 2) {
int winCount = calcWin();
if (winCount > maxWin) {
maxWin = winCount;
int k = 0;
for (int j = 0; j < visited.length; j++) {
if (visited[j]) {
answer[k++] = j + 1; // 인덱스를 1부터 시작하도록 조정
}
}
}
return;
}
for (int i = curr; i < length; i++) {
visited[i] = true;
selectDice(i + 1, depth + 1);
visited[i] = false;
}
}
private static int calcWin() {
// 팀 1과 팀 2의 가능한 합계를 리스트로 생성
List<Integer> team1Sums = generateSums(true);
List<Integer> team2Sums = generateSums(false);
// 팀 2의 합계를 정렬
Collections.sort(team2Sums);
// 승리 횟수 계산
int winCount = 0;
for (int sum1 : team1Sums) {
// 팀 2의 합계 중 sum1보다 작은 값의 개수를 이분 탐색으로 찾음
int count = lowerBound(team2Sums, sum1);
winCount += count;
}
return winCount;
}
private static List<Integer> generateSums(boolean isTeam1) {
List<int[]> diceList = new ArrayList<>();
for (int i = 0; i < length; i++) {
if (visited[i] == isTeam1) {
diceList.add(diceS[i]);
}
}
List<Integer> sums = new ArrayList<>();
sums.add(0); // 초기 합계 0
for (int[] dice : diceList) {
List<Integer> newSums = new ArrayList<>();
for (int sum : sums) {
for (int face : dice) {
newSums.add(sum + face);
}
}
sums = newSums;
}
return sums;
}
private static int lowerBound(List<Integer> list, int target) {
int left = 0;
int right = list.size();
while (left < right) {
int mid = (left + right) / 2;
if (list.get(mid) < target) {
left = mid + 1;
} else {
right = mid;
}
}
return left; // target보다 작은 값의 개수
}
public static void main(String[] args) {
int[][] input = {
{1, 2, 3, 4, 5, 6},
{3, 3, 3, 3, 4, 4},
{1, 3, 3, 4, 4, 4},
{1, 1, 4, 4, 5, 5}
};
int[] answer = solution(input);
System.out.println("최대 승리 횟수를 가진 팀 구성:");
for (int idx : answer) {
System.out.println("주사위 인덱스: " + idx);
}
System.out.println("최대 승리 횟수: " + maxWin);
}
}
모든 조합을 Map<주사위 점수 합, 횟수>로 미리 계산,
keySet(주사위 점수 합)으로 정렬한 리스트를 통해서 승리 횟수를 누적
- Map에 주사위 점수 합을 저장하는 방법이 DP -> 반복되는 연산을 최소화할 수 있음
- 효율적으로 통과
결과, 코드
더보기


package test;
import java.util.*;
public class Solution {
static int length;
static int[][] diceS;
static boolean[] visited;
static int maxWin = 0;
static int[] answer;
public static int[] solution(int[][] dice) {
length = dice.length;
diceS = dice;
visited = new boolean[length];
answer = new int[length / 2];
selectDice(0, 0);
return answer;
}
private static void selectDice(int start, int depth) {
if (depth == length / 2) {
System.out.println("\n[팀 구성 완료]");
System.out.print("팀 1 주사위 인덱스: ");
for (int i = 0; i < length; i++) {
if (visited[i]) {
System.out.print((i + 1) + " ");
}
}
System.out.print("\n팀 2 주사위 인덱스: ");
for (int i = 0; i < length; i++) {
if (!visited[i]) {
System.out.print((i + 1) + " ");
}
}
System.out.println();
int winCount = calcWin();
System.out.println("현재 팀 구성의 승리 횟수: " + winCount);
if (winCount > maxWin) {
System.out.println("최대 승리 횟수를 갱신했습니다!");
maxWin = winCount;
int k = 0;
for (int j = 0; j < visited.length; j++) {
if (visited[j]) {
answer[k++] = j + 1;
}
}
}
return;
}
for (int i = start; i < length; i++) {
visited[i] = true;
selectDice(i + 1, depth + 1);
visited[i] = false;
}
}
private static int calcWin() {
List<int[]> team1 = new ArrayList<>();
List<int[]> team2 = new ArrayList<>();
// 팀 나누기
for (int i = 0; i < length; i++) {
if (visited[i]) {
team1.add(diceS[i]);
} else {
team2.add(diceS[i]);
}
}
// 각 팀의 가능한 합 계산
Map<Integer, Integer> team1SumCounts = generateSumCounts(team1, "팀1");
Map<Integer, Integer> team2SumCounts = generateSumCounts(team2, "팀2");
// 합을 정렬된 리스트로 변환
List<Integer> team1Sums = new ArrayList<>(team1SumCounts.keySet());
List<Integer> team2Sums = new ArrayList<>(team2SumCounts.keySet());
Collections.sort(team1Sums);
Collections.sort(team2Sums);
// 팀 1의 가능한 합과 빈도 출력
System.out.println("\n[팀 1의 가능한 합과 빈도]");
for (int sum : team1Sums) {
System.out.println("합계: " + sum + ", 빈도: " + team1SumCounts.get(sum));
}
// 팀 2의 가능한 합과 빈도 출력
System.out.println("\n[팀 2의 가능한 합과 빈도]");
for (int sum : team2Sums) {
System.out.println("합계: " + sum + ", 빈도: " + team2SumCounts.get(sum));
}
int winCount = 0;
int idxTeam2 = 0;
int totalTeam2Counts = 0;
// 승리 횟수 계산
System.out.println("\n[승리 횟수 계산 시작]");
for (int sum1 : team1Sums) {
int count1 = team1SumCounts.get(sum1);
System.out.println("팀 1 합계: " + sum1 + ", 빈도: " + count1);
// 팀 2의 합계 중 팀 1의 현재 합계보다 작은 합계의 빈도 누적
while (idxTeam2 < team2Sums.size() && team2Sums.get(idxTeam2) < sum1) {
int sum2 = team2Sums.get(idxTeam2);
int count2 = team2SumCounts.get(sum2);
totalTeam2Counts += count2;
System.out.println(" 팀 2 합계: " + sum2 + " (빈도: " + count2 + ") 누적된 팀 2 빈도 합계: " + totalTeam2Counts);
idxTeam2++;
}
int winCountForSum1 = count1 * totalTeam2Counts;
System.out.println(" 현재 팀 1 합계로 이길 수 있는 경우의 수: " + winCountForSum1);
winCount += winCountForSum1;
System.out.println(" 누적 승리 횟수: " + winCount);
}
System.out.println("[승리 횟수 계산 종료]\n");
return winCount;
}
// 합의 빈도를 계산하는 메서드 (합계 계산 과정 출력 포함)
private static Map<Integer, Integer> generateSumCounts(List<int[]> team, String teamName) {
Map<Integer, Integer> sumCounts = new HashMap<>();
sumCounts.put(0, 1);
System.out.println("\n[" + teamName + " 합계 계산 과정]");
int diceNumber = 1;
for (int[] dice : team) {
Map<Integer, Integer> newSumCounts = new HashMap<>();
System.out.println("주사위 " + diceNumber + "의 면: " + Arrays.toString(dice));
for (Map.Entry<Integer, Integer> entry : sumCounts.entrySet()) {
int currentSum = entry.getKey();
int currentCount = entry.getValue();
for (int face : dice) {
int newSum = currentSum + face;
newSumCounts.put(newSum, newSumCounts.getOrDefault(newSum, 0) + currentCount);
System.out.println("현재 합계: " + currentSum + " + 면 " + face + " = 새로운 합계: " + newSum + " (빈도: " + newSumCounts.get(newSum) + ")");
}
}
sumCounts = newSumCounts;
diceNumber++;
System.out.println(teamName + "의 현재 가능한 합계와 빈도: " + sumCounts);
}
return sumCounts;
}
public static void main(String[] args) {
int[][] input = {
{1, 2, 3, 4, 5, 6},
{3, 3, 3, 3, 4, 4},
{1, 3, 3, 4, 4, 4},
{1, 1, 4, 4, 5, 5}
};
int[] answer = solution(input);
System.out.println("\n최대 승리 횟수를 가진 팀 구성:");
for (int idx : answer) {
System.out.println("주사위 인덱스: " + idx);
}
System.out.println("최대 승리 횟수: " + maxWin);
}
}
결론
연산을 획기적으로 감소시키는 과정을 보고 DP를 공부하지 않을 수 없었다...
'프로그래머스' 카테고리의 다른 글
[프로그래머스 JAVA] 순위 (3) | 2024.10.13 |
---|---|
[프로그래머스 JAVA] 수식 복원하기 (0) | 2024.09.19 |
[프로그래머스 JAVA] 이중우선순위큐 (0) | 2024.05.13 |
[프로그래머스 JAVA] 모음사전 (0) | 2024.05.12 |
[프로그래머스 JAVA] 프로세스 (2) | 2024.05.01 |