
이 문제는 코드트리의 일반 연습/기출 문제에 수록되어 있는 메이즈 러너 문제입니다. 특히 23년도 상반기 삼성 SW 역량 테스트 기출 문제로도 잘 알려져 있는 문제입니다.
문제에서 구현해야 할 부분은 크게 3가지입니다.
1. 참가자들을 모두 이동시키기
2. 출구(E)와 최소 한 명의 참가자를 포함하는 최소 넓이 직사각형 구하기
3. 최소 넓이 직사각형을 회전시키기
우선, 문제의 input을 받는 부분과 위 구현의 추상 메서드, output 출력부를 정의해보겠습니다.
import sys
from typing import Tuple
from collections import deque
input = sys.stdin.readline
N, M, K = map(int, input().rstrip().split())
matrix = [list(map(int, input().rstrip().split())) for _ in range(N)]
participants = deque(
[tuple(map(lambda x: int(x) - 1, input().rstrip().split())) for _ in range(M)]
)
E = tuple(map(lambda x: int(x) - 1, input().rstrip().split()))
answer = 0
def move(py: int, px: int) -> int:
pass
def get_rectangle() -> Tuple[int, int, int, int]
pass
def rotate(min_y: int, min_x: int, max_y: int, max_x: int) -> None:
pass
# 최대 k초 이동 가능
for i in range(K):
# 참가자 당 한 번씩 move
M = len(participants)
for _ in range(M):
answer += move(*participants.popleft())
# 이동 과정에서 참가자가 모두 탈출한 경우 종료
if not participants:
break
# 정사각형 좌표를 구해 해당 위치 90도 rotate
rotate(*get_rectangle())
print(answer)
print(E[0] + 1, E[1] + 1)
participants)를 deque으로 받아서 큐로 활용합니다.participants 에서 모든 participant를 한 번씩 pop하여 이동 시킵니다.E)와 participants의 좌표를 고려하여 정사각형 좌표를 구해 해당 위치를 90도 회전시킵니다.answer)와 최종 E를 반환합니다.먼저 참가자를 어떻게 이동시킬지 생각해보겠습니다.
dy = [-1, 1, 0, 0] # 상 하 좌 우 순서
dx = [0, 0, -1, 1]
def move(py: int, px: int) -> int:
"""
특정 참가자를 출구와 가까운 쪽으로 1만큼 이동시킨다.
이동 시 출구와 가까워지는 쪽으로, 상-하-좌-우 우선순위로 이동한다.
input : 이동시킬 참가자 (y, x) 좌표
output : 참가자 이동 여부 (이동했으면 1, 이동하지 않았으면 0)
"""
# 기존 참가자와 출구까지의 최단 거리
dist = abs(py - E[0]) + abs(px - E[1])
for d in range(4):
ny = py + dy[d]
nx = px + dx[d]
# 이동 후 참가자와 출구까지의 최단 거리
new_dist = abs(ny - E[0]) + abs(nx - E[1])
# 이동 가능하며, 출구와 가까워지는 경우
if 0 <= ny < N and 0 <= nx < N and matrix[ny][nx] == 0 and new_dist < dist:
# 이동 후 좌표가 탈출구인 경우 탈출시키고 아닌 경우, 이동시킨 좌표를 큐에 삽입
if (ny, nx) != E:
participants.append((ny, nx))
# 이동 성공 시 count 1 반환
return 1
# 이동 실패 시 원래 좌표를 다시 큐에 삽입, count 0 반환
participants.append((py, px))
return 0
E인 경우는 다시 큐에 넣으면 안되겠죠.participants에 삽입하고 0을 반환합니다.모든 참가자 이동이 끝났으면, E와 participants 를 바탕으로 회전시킬 정사각형의 좌표를 구해야합니다.
def get_rectangle() -> Tuple[int, int, int, int]:
"""
출구와 최소 한 명의 참가자를 포함하는 최소 넓이 직사각형의 네 좌표를 반환한다.
input : x
output : 직사각형의 min_y, min_x, max_y, max_x
"""
rectangles = []
for y, x in set(participants):
max_y = max(y, E[0])
min_y = min(y, E[0])
max_x = max(x, E[1])
min_x = min(x, E[1])
# 한 변의 길이
side_len = max(max_y - min_y, max_x - min_x)
# 한 변의 길이를 유지하는 최소의 좌상단 좌표를 가지는 직사각형 좌표 구하기
min_y = max(max_y - side_len, 0)
max_y = min_y + side_len
min_x = max(max_x - side_len, 0)
max_x = min_x + side_len
rectangles.append((side_len**2, min_y, min_x, max_y, max_x))
# 우선순위 : 넓이 > 좌상단 y좌표 > 좌상단 x좌표
return min(rectangles)[1:]
주의할 점은 크게 두가지입니다.
특정 참가자 좌표와 E사이의 좌상단 좌표가 가장 작은 정사각형의 좌표를 구하는 과정을 그림으로 설명해보겠습니다.
다음과 같이 E는 (2, 1), 참가자는 (0, 1)인 상황을 가정해보겠습니다.

우리는 좌상단 좌표가 최대한 (0, 0)과 가까운 정사각형을 얻어야합니다. 그러면 E와 참가자의 좌표 중 y, x 값이 큰 좌표가 가장 우측 하단으로 멀어져야 할 것이고, 이를 위해서 단순하게 두 좌표 중 max_y, max_x 좌표에 변의 길이를 빼었을 때의 좌표를 좌상단 좌표로 생각하면 되겠죠.
하지만 위의 경우에 변의 길이인 2를 빼었을 때는 0을 벗어나 버립니다.

차선책으로 0을 벗어나지 않는 첫 좌표를 선택할 수 있겠네요.

rectangles.append((side_len**2, min_y, min_x, max_y, max_x))return min(rectangles)[1:]정사각형의 좌표를 알아냈으면 이제 회전시켜야 합니다.
def rotate(min_y: int, min_x: int, max_y: int, max_x: int) -> None:
"""
rotate시킬 정사각형의 좌표를 받아 시계방향으로 90도 회전시킨다.
input : rotate 시킬 정사각형의 min_y, min_x, max_y, max_x 좌표
output : x
"""
global E
participants_set = set(participants)
# rotate 시킬 부분의 임시 matrix
rotate_matrix = [
[0 for _ in range(min_x, max_x + 1)] for _ in range(min_y, max_y + 1)
]
# rotate전 정보 백업
for y in range(min_y, max_y + 1):
for x in range(min_x, max_x + 1):
# 벽의 경우 내구도 -1 시켜 백업
if matrix[y][x] > 0:
rotate_matrix[y - min_y][x - min_x] = matrix[y][x] - 1
# 출구 좌표 정보 백업
elif (y, x) == E:
rotate_matrix[y - min_y][x - min_x] = "E"
# 참가자 좌표 정보 백업
elif (y, x) in participants_set:
# 참가자는 동일 좌표에 여러명이 위치할 수 있음
cnt = 0
while (y, x) in participants:
cnt += 1
participants.remove((y, x))
# 참가자 한 명당 *의 개수로 백업
rotate_matrix[y - min_y][x - min_x] = "*" * cnt
# 해당 백업 matrix 90도 회전
rotate_matrix = [list(row)[::-1] for row in zip(*rotate_matrix)]
# 원본 matrix의 정사각형 위치를 90도 회전시킨 백업 matrix로 대치
for y in range(min_y, max_y + 1):
for x in range(min_x, max_x + 1):
# 회전 후 참가자 위치를 다시 큐에 삽입
if str(rotate_matrix[y - min_y][x - min_x]).startswith("*"):
# 기존 존재하던 참가자 수만큼 삽입
for _ in range(len(rotate_matrix[y - min_y][x - min_x])):
participants.append((y, x))
matrix[y][x] = 0
# 회전 후 출구 위치
elif rotate_matrix[y - min_y][x - min_x] == "E":
E = (y, x)
matrix[y][x] = 0
# 회전 후 벽의 내구도
else:
matrix[y][x] = rotate_matrix[y - min_y][x - min_x]
정사각형의 크기만큼 백업 matrix를 정의해서 rotate 전의 벽의 내구도, 참가자 좌표, 출구 좌표를 백업합니다.
participants에서 모두 빼고, 뺀 숫자만큼 "*"로 백업 매트릭스에 기록합니다.백업 matrix를 시계방향으로 90도 회전시킵니다.
rotate_matrix = [list(row)[::-1] for row in zip(*rotate_matrix)]
회전시킨 백업 matrix의 좌표에 맞춰 원본 matrix를 업데이트 해줍니다.
이 때, 참가자의 좌표도 업데이트 해서 "*" 개수만큼 participants에 삽입해주어야 합니다.
출구도 마찬가지로 기록된 정보를 바탕으로 업데이트 해줍니다.
전체 코드를 참고하실 분은 아래 제가 참여하고 있는 알고리즘 스터디 Repository를 확인해주세요!
🙏 문제 접근 방법 및 코드에 대한 피드백과 질문은 환영입니다!
본 문제의 다른 풀이 및 피드백, 전체 문제 풀이 순서는 위 알고리즘 스터디 Repository에서도 확인 가능합니다.