https://school.programmers.co.kr/learn/courses/30/lessons/169199
문제 설명
리코쳇 로봇이라는 보드게임이 있습니다.
이 보드게임은 격자모양 게임판 위에서 말을 움직이는 게임으로, 시작 위치에서 목표 위치까지 최소 몇 번만에 도달할 수 있는지 말하는 게임입니다.
이 게임에서 말의 움직임은 상, 하, 좌, 우 4방향 중 하나를 선택해서 게임판 위의 장애물이나 맨 끝에 부딪힐 때까지 미끄러져 이동하는 것을 한 번의 이동으로 칩니다.
다음은 보드게임판을 나타낸 예시입니다.
여기서 "."은 빈 공간을, "R"은 로봇의 처음 위치를, "D"는 장애물의 위치를, "G"는 목표지점을 나타냅니다.
위 예시에서는 "R" 위치에서 아래, 왼쪽, 위, 왼쪽, 아래, 오른쪽, 위 순서로 움직이면 7번 만에 "G" 위치에 멈춰 설 수 있으며, 이것이 최소 움직임 중 하나입니다.
게임판의 상태를 나타내는 문자열 배열 board가 주어졌을 때, 말이 목표위치에 도달하는데 최소 몇 번 이동해야 하는지 return 하는 solution함수를 완성하세요. 만약 목표위치에 도달할 수 없다면 -1을 return 해주세요.
제한 사항
- 3 ≤ board의 길이 ≤ 100
- 3 ≤ board의 원소의 길이 ≤ 100
- board의 원소의 길이는 모두 동일합니다.
- 문자열은 ".", "D", "R", "G"로만 구성되어 있으며 각각 빈 공간, 장애물, 로봇의 처음 위치, 목표 지점을 나타냅니다.
- "R"과 "G"는 한 번씩 등장합니다.
풀이
게임판 (= graph), 목표위치에 도달하는 `최소 몇 번 이동`. 이 두가지 요소를보고 BFS를 떠올려야 한다.
여기서 특이점은 미끄러져서 벽이나 장애물에 부딛힐 때까지 이동한다는 것.
로직
- 가로 (col), 세로 (row), 장애물 (stoppers), 상하좌우 4방향 (directions), 방문여부 (visited) 초기화
- 주어진 board를 돌며 시작지점 `R`과 목표 `G`, 장애물 `D`를 찾음
- 시작지점을 queue에 넣고 BFS 시작.
- BFS중 특이점은 다음 정점을 찾을 때 각 방향마다 끝까지 이동 후 queue에 저장
초기 코드
# 초기 코드
from collections import deque
def solution(board):
col = len(board[0])
row = len(board)
stoppers = set()
for i in range(row):
for j in range(col):
if board[i][j] == 'R':
queue = deque([(i, j, 0)])
elif board[i][j] == 'G':
goal = (i, j)
elif board[i][j] == 'D':
stoppers.add((i, j))
directions = [(1,0),(-1,0),(0,1),(0,-1)]
visited = set()
while queue:
current_node = queue.popleft()
y, x, cnt = current_node
if (y, x) not in visited:
visited.add((y, x))
for dy, dx in directions:
ny, nx = y+dy, x+dx
while 0 <= ny < row and 0 <= nx < col and (ny, nx) not in stoppers:
ny += dy
nx += dx
vertex = (ny-dy, nx-dx)
stop = (ny-dy, nx-dx, cnt+1)
if vertex == goal:
return cnt+1
if vertex not in visited:
queue.append(stop)
return -1
# 15.71ms, 10.4MB
추가로 뭔가 개선할 점이 필요할까 생각했는데 몇가지 개선할 점이 보였다.
- 장애물 `stoppers`, 목표지점 `G`를 저장할 필요가 없다 : 따로 저장하지 않더라도 접근하는데 O(1)이다.
- 시작지점 `R`만 찾는 함수를 따로 빼는것이 가독성이 좋아보인다.
- 방문여부 `visited`를 2차원 리스트로 하는것이 시공간적으로 이득이다.
1차 개선 : 로직 수정
# 1차 개선
from collections import deque
def find_start(col, row, board):
for i in range(row):
for j in range(col):
if board[i][j] == 'R':
return deque([(i, j, 0)])
def solution(board):
col, row = len(board[0]), len(board)
directions = [(1,0),(-1,0),(0,1),(0,-1)]
visited = [[0 for _ in range(col)] for _ in range(row)]
queue = find_start(col, row, board)
while queue:
current_node = queue.popleft()
y, x, cnt = current_node
for dy, dx in directions:
ny, nx = y+dy, x+dx
while 0 <= ny < row and 0 <= nx < col and board[ny][nx] != 'D':
ny += dy
nx += dx
ny, nx = ny-dy, nx-dx
if board[ny][nx] == 'G':
return cnt+1
if not visited[ny][nx]:
visited[ny][nx] = 1
queue.append((ny, nx, cnt+1))
return -1
# 7.12ms, 10.3MB