PGMS_2022 Kakao_사라지는 발판 (Java)
[level 3] 사라지는 발판 - 92345
성능 요약
메모리: 83.5 MB, 시간: 13.12 ms
구분
코딩테스트 연습 > 2022 KAKAO BLIND RECRUITMENT
채점결과
정확성: 100.0
합계: 100.0 / 100.0
제출 일자
2025년 04월 02일 03:31:41
문제 설명
문제 설명
플레이어 A와 플레이어 B가 서로 게임을 합니다. 당신은 이 게임이 끝날 때까지 양 플레이어가 캐릭터를 몇 번 움직이게 될지 예측하려고 합니다.
각 플레이어는 자신의 캐릭터 하나를 보드 위에 올려놓고 게임을 시작합니다. 게임 보드는 1x1 크기 정사각 격자로 이루어져 있으며, 보드 안에는 발판이 있는 부분과 없는 부분이 있습니다. 발판이 있는 곳에만 캐릭터가 서있을 수 있으며, 처음 캐릭터를 올려놓는 곳은 항상 발판이 있는 곳입니다. 캐릭터는 발판이 있는 곳으로만 이동할 수 있으며, 보드 밖으로 이동할 수 없습니다. 밟고 있던 발판은 그 위에 있던 캐릭터가 다른 곳으로 이동하여 다른 발판을 밞음과 동시에 사라집니다. 양 플레이어는 번갈아가며 자기 차례에 자신의 캐릭터를 상하좌우로 인접한 4개의 칸 중에서 발판이 있는 칸으로 옮겨야 합니다.
다음과 같은 2가지 상황에서 패자와 승자가 정해지며, 게임이 종료됩니다.
- 움직일 차례인데 캐릭터의 상하좌우 주변 4칸이 모두 발판이 없거나 보드 밖이라서 이동할 수 없는 경우, 해당 차례 플레이어는 패배합니다.
- 두 캐릭터가 같은 발판 위에 있을 때, 상대 플레이어의 캐릭터가 다른 발판으로 이동하여 자신의 캐릭터가 서있던 발판이 사라지게 되면 패배합니다.
게임은 항상 플레이어 A가 먼저 시작합니다. 양 플레이어는 최적의 플레이를 합니다. 즉, 이길 수 있는 플레이어는 최대한 빨리 승리하도록 플레이하고, 질 수밖에 없는 플레이어는 최대한 오래 버티도록 플레이합니다. '이길 수 있는 플레이어'는 실수만 하지 않는다면 항상 이기는 플레이어를 의미하며, '질 수밖에 없는 플레이어'는 최선을 다해도 상대가 실수하지 않으면 항상 질 수밖에 없는 플레이어를 의미합니다. 최대한 오래 버틴다는 것은 양 플레이어가 캐릭터를 움직이는 횟수를 최대화한다는 것을 의미합니다.
아래 그림은 초기 보드의 상태와 각 플레이어의 위치를 나타내는 예시입니다.
위와 같은 경우, 플레이어 A는 실수만 하지 않는다면 항상 이길 수 있습니다. 따라서 플레이어 A는 이길 수 있는 플레이어이며, B는 질 수밖에 없는 플레이어입니다. 다음은 A와 B가 최적의 플레이를 하는 과정을 나타냅니다.
- 플레이어 A가 초기 위치 (1, 0)에서 (1, 1)로 이동합니다. 플레이어 A가 (0, 0)이나 (2, 0)으로 이동할 경우 승리를 보장할 수 없습니다. 따라서 무조건 이길 방법이 있는 (1, 1)로 이동합니다.
- 플레이어 B는 (1, 1)로 이동할 경우, 바로 다음 차례에 A가 위 또는 아래 방향으로 이동하면 발판이 없어져 패배하게 됩니다. 질 수밖에 없는 플레이어는 최대한 오래 버티도록 플레이하기 때문에 (1, 1)로 이동하지 않습니다. (1, 2)에서 위쪽 칸인 (0, 2)로 이동합니다.
- A가 (1, 1)에서 (0, 1)로 이동합니다.
- B에게는 남은 선택지가 (0, 1)밖에 없습니다. 따라서 (0, 2)에서 (0, 1)로 이동합니다.
- A가 (0, 1)에서 (0, 0)으로 이동합니다. 이동을 완료함과 동시에 B가 서있던 (0, 1)의 발판이 사라집니다. B가 패배합니다.
- 만약 과정 2에서 B가 아래쪽 칸인 (2, 2)로 이동하더라도 A는 (2, 1)로 이동하면 됩니다. 이후 B가 (2, 1)로 이동, 다음 차례에 A가 (2, 0)으로 이동하면 B가 패배합니다.
위 예시에서 양 플레이어가 최적의 플레이를 했을 경우, 캐릭터의 이동 횟수 합은 5입니다. 최적의 플레이를 하는 방법은 여러 가지일 수 있으나, 이동한 횟수는 모두 5로 같습니다.
게임 보드의 초기 상태를 나타내는 2차원 정수 배열 board
와 플레이어 A의 캐릭터 초기 위치를 나타내는 정수 배열 aloc
, 플레이어 B의 캐릭터 초기 위치를 나타내는 정수 배열 bloc
이 매개변수로 주어집니다. 양 플레이어가 최적의 플레이를 했을 때, 두 캐릭터가 움직인 횟수의 합을 return 하도록 solution 함수를 완성해주세요.
제한사항
- 1 ≤
board
의 세로 길이 ≤ 5 - 1 ≤
board
의 가로 길이 ≤ 5 board
의 원소는 0 또는 1입니다.- 0은 발판이 없음을, 1은 발판이 있음을 나타냅니다.
- 게임 보드의 좌측 상단 좌표는 (0, 0), 우측 하단 좌표는 (
board
의 세로 길이 - 1,board
의 가로 길이 - 1)입니다.
aloc
과bloc
은 각각 플레이어 A의 캐릭터와 플레이어 B의 캐릭터 초기 위치를 나타내는 좌표값이며 [r, c] 형태입니다.- r은 몇 번째 행인지를 나타냅니다.
- 0 ≤ r <
board
의 세로 길이 - c는 몇 번째 열인지를 나타냅니다.
- 0 ≤ c <
board
의 가로 길이 - 초기 보드의
aloc
과bloc
위치는 항상 발판이 있는 곳입니다. aloc
과bloc
이 같을 수 있습니다.
- 상대 플레이어의 캐릭터가 있는 칸으로 이동할 수 있습니다.
입출력 예
board | aloc | bloc | result |
---|---|---|---|
[[1, 1, 1], [1, 1, 1], [1, 1, 1]] | [1, 0] | [1, 2] | 5 |
[[1, 1, 1], [1, 0, 1], [1, 1, 1]] | [1, 0] | [1, 2] | 4 |
[[1, 1, 1, 1, 1]] | [0, 0] | [0, 4] | 4 |
[[1]] | [0, 0] | [0, 0] | 0 |
입출력 예 설명
입출력 예 #1
문제 예시와 같습니다.
입출력 예 #2
주어진 조건을 그림으로 나타내면 아래와 같습니다.
항상 이기는 플레이어는 B, 항상 지는 플레이어는 A입니다.
다음은 B가 이기는 방법 중 하나입니다.
- A가 (1, 0)에서 (0, 0)으로 이동
- B가 (1, 2)에서 (2, 2)로 이동
- A가 (0, 0)에서 (0, 1)로 이동
- B가 (2, 2)에서 (2, 1)로 이동
- A가 (0, 1)에서 (0, 2)로 이동
- B가 (2, 1)에서 (2, 0)으로 이동
- A는 더 이상 이동할 수 없어 패배합니다.
위와 같이 플레이할 경우 이동 횟수 6번 만에 게임을 B의 승리로 끝낼 수 있습니다.
B가 다음과 같이 플레이할 경우 게임을 더 빨리 끝낼 수 있습니다. 이길 수 있는 플레이어는 최대한 빨리 게임을 끝내려 하기 때문에 위 방법 대신 아래 방법을 선택합니다.
- A가 (1, 0)에서 (0, 0)으로 이동
- B가 (1, 2)에서 (0, 2)로 이동
- A가 (0, 0)에서 (0, 1)로 이동
- B가 (0, 2)에서 (0, 1)로 이동
- A는 더 이상 이동할 수 없어 패배합니다.
위와 같이 플레이할 경우 이동 횟수 4번 만에 게임을 B의 승리로 끝낼 수 있습니다. 따라서 4를 return 합니다.
입출력 예 #3
양 플레이어는 매 차례마다 한 가지 선택지밖에 고를 수 없습니다. 그 결과, (0, 2)에서 어디로도 이동할 수 없는 A가 패배합니다. 양 플레이어가 캐릭터를 움직인 횟수의 합은 4입니다.
입출력 예 #4
게임을 시작하는 플레이어 A가 처음부터 어디로도 이동할 수 없는 상태입니다. 따라서 A의 패배이며, 이동 횟수의 합은 0입니다.
제한시간 안내
- 정확성 테스트 : 10초
출처: 프로그래머스 코딩 테스트 연습, https://school.programmers.co.kr/learn/challenges
문제 풀이
- DFS를 이용하여 모든 가능한 게임 상태를 탐색
- 각 상태에서 현재 플레이어의 최적 전략을 결정
- 재귀적으로 게임 결과 계산
승패 판단
- 현재 플레이어가 이동한 후, 다음 플레이어의 상태를 확인
- dfs 반환 이동 횟수가 짝수: 다음 플레이어가 더 이상 움직일 수 없음 → 현재 플레이어 승리
- 0으로 생각해보면 쉬움 : 0회 움직일 수 있다는건 이제 못움직이므로 그사람(지금 플레이어 기준 다음 사람일 것이다) 패배. 즉 지금 플레이어 승리
- dfs 반환 이동 횟수가 홀수: 다음 플레이어 이후에 현재 플레이어가 움직일 수 없음 → 현재 플레이어 패배
\(O(4^{(N*M)})\) 이지만, 백트래킹하면 괜찮다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class Solution {
static int[] dr = {-1, 0, 1, 0}, dc = {0, -1, 0, 1};
static int res = 0;
static int N, M;
public int solution(int[][] board, int[] aloc, int[] bloc) {
N = board.length;
M = board[0].length;
res = dfs(board, aloc, bloc, 0);
return res;
}
private int dfs(int[][] board, int[] aloc, int[] bloc, int order){
// 순서 0이면 a, 1면 b
int currR, currC;
currR = order == 0 ? aloc[0] : bloc[0];
currC = order == 0 ? aloc[1] : bloc[1];
// 전 사람이 이기면 끝
if(board[currR][currC] == 0 || !canMove(board, currR, currC)) return 0;
int currWin = Integer.MAX_VALUE;
int currLose = 0;
boolean isCurrWin = false;
// 지금 발판 없애기
board[currR][currC] = 0;
for(int k=0; k<4; k++){
int nr = currR + dr[k];
int nc = currC + dc[k];
if(isValid(nr, nc) && board[nr][nc] == 1){
int nextCnt; // 이후 나의 이동횟수
nextCnt = order == 0 ? dfs(board, new int[] {nr, nc}, bloc, 1-order) : dfs(board, aloc, new int[] {nr, nc}, 1-order);
// 다음 플레이어가 짐 -> 지금 플레이어 최대한 빨리이기기
if(nextCnt%2 == 0){
currWin = Math.min(currWin, nextCnt + 1);
isCurrWin = true;
}
// 다음 플레이어가 이김 -> 지금 플레이어 최대한 늦게지기
else currLose = Math.max(currLose, nextCnt + 1);
}
}
board[currR][currC] = 1;
return isCurrWin ? currWin : currLose;
}
private static boolean canMove(int[][] board, int r, int c){
for (int k = 0; k < 4; k++) {
int nr = r + dr[k];
int nc = c + dc[k];
if (isValid(nr, nc) && board[nr][nc] == 1) return true;
}
return false;
}
private static boolean isValid(int r, int c){
return r >= 0 && r < N && c >= 0 && c < M;
}
}