Algorithm/Baekjoon

[Python | 7562] 나이트의 이동

dotz0ver 2025. 4. 13. 21:53

https://www.acmicpc.net/problem/7562


문제 설명

체스판 위에 한 나이트가 놓여져 있다. 나이트가 한 번에 이동할 수 있는 칸은 아래 그림에 나와있다. 나이트가 이동하려고 하는 칸이 주어진다. 나이트는 몇 번 움직이면 이 칸으로 이동할 수 있을까?

 

📌 시간 제한: 1


입력

입력의 첫째 줄에는 테스트 케이스의 개수가 주어진다.

각 테스트 케이스는 세 줄로 이루어져 있다. 첫째 줄에는 체스판의 한 변의 길이 l(4 ≤ l ≤ 300)이 주어진다. 체스판의 크기는 l × l이다. 체스판의 각 칸은 두 수의 쌍 {0, ..., l-1} × {0, ..., l-1}로 나타낼 수 있다. 둘째 줄과 셋째 줄에는 나이트가 현재 있는 칸, 나이트가 이동하려고 하는 칸이 주어진다.


출력

각 테스트 케이스마다 나이트가 최소 몇 번만에 이동할 수 있는지 출력한다.


문제 접근

체스판 위에서 나이트가 한 위치에서 다른 위치로 이동할 때, 최소 몇 번만에 도달할 수 있는지를 묻는 문제다. 나이트는 "ㄱ"자 형태로 총 8가지 방향으로 움직일 수 있고, 체스판의 크기 l, 시작 위치 (x1, y1), 목표 위치 (x2, y2)가 입력으로 주어진다. 이때 목표 위치까지 이동하는 데 필요한 최소 이동 횟수를 구하는 것이 목적이다.

문제를 보면 이동에 가중치가 없고, 최소 이동 횟수를 구하는 구조이기 때문에 BFS(너비 우선 탐색)이 바로 떠오른다. 체스판을 2차원 배열로 보고 각 칸을 하나의 노드로 간주하며, 나이트가 갈 수 있는 다음 위치들을 그래프의 간선처럼 연결해 탐색을 수행한다.

 

방법

  • 나이트의 이동은 8가지 방향으로 정의된다.
    → dx = [2, 1, -1, -2, -2, -1, 1, 2]
    → dy = [1, 2, 2, 1, -1, -2, -2, -1]
  • 시작 위치에서부터 BFS를 돌리며, visited[x][y] 배열을 통해
    방문 여부 + 이동 횟수를 함께 관리한다.
  • 도착 지점에 처음 도달하는 순간이 곧 최단 거리이므로
    → 그 값을 출력하고 탐색을 종료하면 된다.

시간 복잡도

  • 한 테스트케이스에서 방문 가능한 칸은 최대 l × l
  • 각 칸은 한 번만 방문하므로
     O(l²)
  • 테스트케이스가 T개 주어지므로 전체 복잡도는
     O(T × l²)
  • 최대 300 × 300 × 100 = 9,000,000 → 파이썬으로 충분히 처리 가능

구현

  • visited 배열에 이동 횟수를 저장하는 방식으로, 거리 계산과 방문 여부를 동시에 처리
  • 시작 지점을 1로 초기화했기 때문에, 출력할 때는 -1을 해준다
  • 도착 좌표에 도달하면 즉시 break 하므로, 불필요한 탐색이 없다
from collections import deque

# 나이트의 8가지 이동 방향 정의 (x, y 좌표 변화)
dx = [2, 1, -1, -2, -2, -1, 1, 2]
dy = [1, 2, 2, 1, -1, -2, -2, -1]

T = int(input())

for _ in range(T):
    l = int(input())  # 체스판 한 변의 길이
    x1, y1 = map(int, input().split())  # 시작 위치
    x2, y2 = map(int, input().split())  # 목표 위치

    # 방문 여부 및 이동 횟수를 기록할 2차원 배열
    visited = [[0] * l for _ in range(l)]

    queue = deque()
    queue.append((x1, y1))  # 시작 위치 큐에 삽입
    visited[x1][y1] = 1     # 시작 위치 방문 처리 (이동 횟수 1로 시작)

    while queue:
        x, y = queue.popleft()  # 현재 위치

        # 목표 지점에 도달하면 이동 횟수 출력 후 종료
        if x == x2 and y == y2:
            print(visited[x][y] - 1)  # 시작점을 1로 했으므로 -1 보정
            break

        # 8방향으로 이동 가능한 좌표 탐색
        for i in range(8):
            nx = x + dx[i]
            ny = y + dy[i]

            # 체스판 범위 내 && 아직 방문하지 않은 위치라면 이동
            if 0 <= nx < l and 0 <= ny < l and visited[nx][ny] == 0:
                visited[nx][ny] = visited[x][y] + 1  # 이동 횟수 갱신
                queue.append((nx, ny))  # 다음 위치 큐에 추가

 

위처럼 일반적으로 BFS는 큐를 이용해 레벨 순서대로 탐색을 진행한다. 하지만 이 문제처럼 이동 범위가 제한적이고, 레벨 단위 탐색이 필요한 경우에는 큐 없이도 BFS를 재귀적으로 구현할 수 있다.

이 코드에서는 각 재귀 호출이 하나의 탐색 레벨에 해당하며, 현재 레벨의 위치들을 리스트로 묶어 반복문으로 순회한 뒤, 다음 레벨에 해당하는 좌표들을 새 리스트에 담아 재귀로 넘긴다. 이 과정을 통해 큐 없이도 BFS의 핵심인 레벨 순 탐색을 자연스럽게 유지할 수 있다.

다만 이는 큐를 명시적으로 사용하지 않고, 리스트와 재귀 호출로 그 동작을 흉내낸 구조로 볼 수 있다. BFS의 논리는 따르지만 전통적인 구현은 아니며, 특히 탐색 깊이가 깊어질 경우에는 파이썬의 재귀 호출 제한 때문에 비효율적일 수 있다는 점도 고려해야 한다.

# 나이트가 이동할 수 있는 8가지 방향
dx = [2, 1, -1, -2, -2, -1, 1, 2]
dy = [1, 2, 2, 1, -1, -2, -2, -1]

# 재귀적으로 BFS 수행
def bfs_recursive(current_level, visited, x2, y2, l, depth):
    next_level = []  # 다음 레벨에서 탐색할 좌표들 저장
    for x, y in current_level:
        if x == x2 and y == y2:
            print(depth)  # 목표 지점 도달 시 현재 깊이 출력
            return True
        for i in range(8):
            nx, ny = x + dx[i], y + dy[i]
            # 체스판 범위 내이며 방문하지 않은 좌표라면 이동
            if 0 <= nx < l and 0 <= ny < l and not visited[nx][ny]:
                visited[nx][ny] = True
                next_level.append((nx, ny))
    if not next_level:
        return False  # 다음 탐색할 좌표가 없으면 종료
    return bfs_recursive(next_level, visited, x2, y2, l, depth + 1)  # 다음 레벨 탐색

T = int(input())  # 테스트케이스 수 입력
for _ in range(T):
    l = int(input())  # 체스판 크기
    x1, y1 = map(int, input().split())  # 시작 좌표
    x2, y2 = map(int, input().split())  # 목표 좌표

    visited = [[False] * l for _ in range(l)]  # 방문 여부 배열
    visited[x1][y1] = True  # 시작 위치 방문 처리
    bfs_recursive([(x1, y1)], visited, x2, y2, l, 0)  # 재귀 BFS 시작

'Algorithm > Baekjoon' 카테고리의 다른 글

[Python | 2580] 스도쿠  (1) 2025.04.20
[Python | 5014] 스타트링크  (0) 2025.04.13
[Python | 9465] 스티커  (0) 2025.04.07
[Python | 2156] 포도주 시식  (0) 2025.04.06
[Python | 2579] 계단 오르기  (0) 2025.04.06