ROR 게임은 두 팀으로 나누어서 진행하며, 상대 팀 진영을 먼저 파괴하면 이기는 게임입니다. 따라서, 각 팀은 상대 팀 진영에 최대한 빨리 도착하는 것이 유리합니다.
지금부터 당신은 한 팀의 팀원이 되어 게임을 진행하려고 합니다. 다음은 5 x 5 크기의 맵에, 당신의 캐릭터가 (행: 1, 열: 1) 위치에 있고, 상대 팀 진영은 (행: 5, 열: 5) 위치에 있는 경우의 예시입니다.
위 그림에서 검은색 부분은 벽으로 막혀있어 갈 수 없는 길이며, 흰색 부분은 갈 수 있는 길입니다. 캐릭터가 움직일 때는 동, 서, 남, 북 방향으로 한 칸씩 이동하며, 게임 맵을 벗어난 길은 갈 수 없습니다.
아래 예시는 캐릭터가 상대 팀 진영으로 가는 두 가지 방법을 나타내고 있습니다.
첫 번째 방법은 11개의 칸을 지나서 상대 팀 진영에 도착했습니다.
두 번째 방법은 15개의 칸을 지나서 상대팀 진영에 도착했습니다.
위 예시에서는 첫 번째 방법보다 더 빠르게 상대팀 진영에 도착하는 방법은 없으므로, 이 방법이 상대 팀 진영으로 가는 가장 빠른 방법입니다.
만약, 상대 팀이 자신의 팀 진영 주위에 벽을 세워두었다면 상대 팀 진영에 도착하지 못할 수도 있습니다. 예를 들어, 다음과 같은 경우에 당신의 캐릭터는 상대 팀 진영에 도착할 수 없습니다.
게임 맵의 상태 maps가 매개변수로 주어질 때, 캐릭터가 상대 팀 진영에 도착하기 위해서 지나가야 하는 칸의 개수의 최솟값을 return 하도록 solution 함수를 완성해주세요. 단, 상대 팀 진영에 도착할 수 없을 때는 -1을 return 해주세요.
처음에는 생각나는 대로 바로 재귀함수를 이용한 DFS로 짜서 돌렸다. 그런데, 분명 풀이는 맞는데 오류가 떠서 봤더니 다음과 같았다.
Maximum call stack size exceeded
그래서 최단거리 유형의 문제에 대해서 더 찾아보았는데, 최단거리 유형의 문제는 DFS(재귀함수 이용)
보단 BFS(Queue 이용)
로 푸는 것이 더 효율적이고 빠르다는 것을 알았다.
(DFS는 쭉 내려갔을 때 그게 정답이 아닐 확률이 매우 높은데 BFS는 정답을 찾으면 그게 딱 최단거리 그 자체이다.)
그래서 BFS를 사용하여 풀어보았다.
(참고한 풀이: https://hogumachu.tistory.com/9)
visited
배열
게임판과 똑같은 모양으로(2차원 배열) 만들어서 방문하지 않은 칸은 false
, 방문한 칸은 true
로 표시한다. (Array.from()
메소드와 Array.prototype.fill()
메소드를 이용하여 2차원 배열을 선언 및 0으로 초기화시킨다.)
queue
배열
우리가 앞으로 방문해야 할 게임판의 칸의 정보를 담고 있다. (각 원소는 [row, col, count]
식의 정보를 담음)
queue
에서 하나씩 빼면서 해당 칸을 방문한 적 없으면 visit
함수를 실행하고, 해당 칸이 목적지이면 바로 answer
에 대입해서 반환한다.
visit
함수: 방문 처리 및 다음 턴에 방문할 칸들을 찾아서 큐에 넣어주는 함수
파라미터로 전달받은 칸을 visited
배열에 추가하고, 해당 칸을 동/서/남/북으로 움직였을 때 각 칸이 유효한 칸인지 검사한 다음, 유효한 칸만 queue
에 push해준다.
function solution(maps) {
const n = maps.length;
const m = maps[0].length;
let answer = -1;
// 이미 방문한 적 있는 노드를 저장할 배열 (맵과 같은 모양으로)
let visited = Array.from(Array(n), () => Array(m).fill(false));
// 방문해야 할 노드를 채워넣은 큐 (초기값도 같이 넣어준다)
let queue = [[0, 0, 1]];
// 동서남북의 움직임을 표현하기 위한 배열 선언
let dx = [1, -1, 0, 0];
let dy = [0, 0, -1, 1];
// BFS를 진행: 방문할 노드가 없을 때까지 계속한다.
while(queue.length > 0){
// 일단 queue 맨 앞을 꺼낸다.
const current = queue.shift();
// 만약 이게 도착지이면
if(current[0] === n-1 && current[1] === m-1) {
answer = current[2];
break;
}
// 꺼낸 값이 방문하지 않았던 위치라면, 방문시킨다.
if(!visited[current[0]][current[1]])
visit(current[0], current[1], current[2]);
}
function visit(row, col, count) {
visited[row][col] = true;
// 현재 방문한 위치에서 새로 이동할 곳을 찾아준다.
for(let i=0; i<4; i++) {
const next_row = row + dx[i];
const next_col = col + dy[i];
// 1. 다음 위치는 게임의 맵 내부에 있어야 하며,
// 2. 방문하지도 않았어야 하고,
// 3. 벽이 아니어야 한다.
// 위 3개의 조건을 만족한다면 방문할 큐에 넣어준다.
if(next_row >= 0 && next_row < n && next_col >= 0 && next_col < m && !visited[next_row][next_col] && maps[next_row][next_col] === 1)
queue.push([next_row, next_col, count+1])
}
}
return answer;
}
이 코드 같은 경우엔 통과하긴 했지만, 사실 잘못 된 코드이다.
BFS에서는 방문처리를 큐에 삽입할 때 해야 한다!! (진짜 방문했을 때 방문처리 하는 건 DFS에서 하는 것)
from collections import deque
def solution(maps):
n = len(maps) #행 수
m = len(maps[0]) #열 수
#상하좌우 정의
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
answer = -1
visited = [[False] * m for _ in range(n)]
def bfs():
queue = deque()
queue.append([0, 0, 1])
nonlocal answer
while len(queue):
currentRow, currentCol, cnt = queue.popleft()
if visited[currentRow][currentCol] == False:
visited[currentRow][currentCol] = True
if currentRow == n-1 and currentCol == m-1:
answer = cnt
break
for i in range(4):
nextRow = currentRow + dy[i]
nextCol = currentCol + dx[i]
if nextCol>=0 and nextRow>=0 and nextCol<m and nextRow<n and maps[nextRow][nextCol] != 0 and not visited[nextRow][nextCol]:
queue.append([nextRow, nextCol, cnt+1])
bfs()
return answer