백준 2178은 그냥 좌표형 BFS 최단거리 문제다.
핵심은 복잡하지 않다.
(0, 0)에서 시작해서 BFS로 한 칸씩 퍼져 나가면서, 처음 도달한 값이 곧 최단거리라는 점만 잡으면 된다.
다만 이 문제는 일반 그래프 BFS랑 다르게
노드가 숫자 하나가 아니라 좌표 (x, y) 라는 점,
그리고 인접 노드를 인접 리스트가 아니라 상하좌우 직접 계산으로 찾는다는 점에서 한 번 헷갈리기 쉽다.
나도 처음엔 BFS라는 건 바로 보였는데,
막상 구현할 때는
- 좌표를 큐에 어떻게 넣을지
- 방문 체크를 따로 할지
- 거리 기록을 어디에 할지
이 부분에서 한 번씩 생각을 더 하게 됐다.
이번 글은 이 문제를 기준으로
좌표형 BFS를 어떻게 구현하는지 정리해보려고 한다.
🔗 문제 링크
백준 2178 - 미로 탐색
https://www.acmicpc.net/problem/2178
문제 정리
문제 조건은 이렇다.
- 1은 이동 가능한 칸
- 0은 이동 불가능한 칸
- 시작점은 (0, 0)
- 도착점은 (N-1, M-1)
- 지나야 하는 칸 수의 최솟값을 구해야 한다
여기서 바로 봐야 하는 건 이것들이다.
- 최단거리
- 격자
- 상하좌우 이동
- 가중치 없음
이 조합이면 그냥 BFS로 가면 된다.
✅ 핵심 아이디어
이 문제는 이렇게 보면 된다.
- 미로를 2차원 리스트로 받는다
- BFS 큐에는 (x, y) 좌표를 넣는다
- 상하좌우 4방향을 보면서 이동 가능한 칸을 찾는다
- 처음 도달한 칸은 현재 칸 값 + 1로 갱신한다
- 마지막 graph[n-1][m-1] 값이 최단거리다
즉, 이 문제는
그래프 BFS를 좌표형 BFS로 바꿔서 쓰는 문제다.
정답
from collections import deque
n, m = map(int, input().split())
graph = [list(map(int, input().strip())) for _ in range(n)]
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
q = deque()
q.append((0, 0))
while q:
x, y = q.popleft()
for i in range(4):
nx = x + dx[i]
ny = y + dy[i]
if nx < 0 or nx >= n or ny < 0 or ny >= m:
continue
if graph[nx][ny] == 0:
continue
if graph[nx][ny] == 1:
graph[nx][ny] = graph[x][y] + 1
q.append((nx, ny))
print(graph[n - 1][m - 1])
생각해야 하는 부분
이 문제는 BFS라고 끝나는 게 아니라,
일반 그래프 BFS랑 뭐가 다른지를 먼저 봐야 한다.
1. 노드는 도시 번호가 아니라 좌표다
일반 그래프 문제에서는 보통 큐에 이런 게 들어간다.
q.append(1)
근데 미로 탐색은 그게 아니다.
이 문제에서 노드는 미로의 한 칸이다.
즉 큐에 들어가는 건 정수 하나가 아니라 좌표 두 개다.
q.append((0, 0))
이 문제를 제대로 풀려면
애초에 노드를 “숫자”로 보지 말고 위치 정보로 보는 게 중요하다.
2. 인접한 곳은 인접 리스트가 아니라 직접 계산한다
그래프 문제에서는 보통 이런 식으로 간다.
for nxt in graph[now]:
...
근데 미로 탐색은 인접 리스트가 없다.
상하좌우 4칸을 직접 계산해야 한다.
그래서 dx, dy를 둔다.
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
그리고 현재 위치 (x, y)에서 다음 위치 (nx, ny)를 만든다.
nx = x + dx[i]
ny = y + dy[i]
즉, 이 문제는 인접 노드를 직접 생성하는 BFS다.
3. 거리 기록은 graph 자체에 바로 한다
이 문제는 별도의 distance 배열을 만들 수도 있다.
근데 이 문제에서는 graph 자체를 재활용하는 방식이 훨씬 자주 쓰인다.
핵심은 이 줄이다.
graph[nx][ny] = graph[x][y] + 1
현재 칸까지의 거리 값이 이미 들어 있으니까,
새로 도달한 칸은 거기서 +1 하면 된다.
즉,
- 원래 1은 그냥 “갈 수 있다”는 뜻인데
- BFS가 진행되면서 그 칸은 “최단거리 값”으로 바뀐다
이 구조다.
이게 이 문제에서 제일 깔끔한 포인트다.
4. 처음 도달한 값이 곧 최단거리다
이건 BFS 문제에서 항상 중요하다.
BFS는 가까운 칸부터 순서대로 퍼져 나가기 때문에,
어떤 칸에 처음 도달했을 때의 거리가 곧 최단거리다.
그래서
if graph[nx][ny] == 1:
이 조건이 중요하다.
아직 방문 안 한 길만 갱신해야
가장 먼저 도달한 값이 유지된다.
예외처리 / 주의할 점
⚠️ 1. 범위 체크를 먼저 해야 한다
격자 문제는 무조건 이게 먼저다.
if nx < 0 or nx >= n or ny < 0 or ny >= m:
continue
범위 밖으로 나간 좌표를 먼저 걸러내지 않으면
인덱스 에러가 바로 난다.
이건 좌표형 문제에서 거의 기본이다.
⚠️ 2. 0은 갈 수 없는 칸이다
if graph[nx][ny] == 0:
continue
이 문제에서 0은 벽이다.
즉, 이동 대상이 아니다.
1만 이동 가능한 칸이다.
이 부분을 그냥 “숫자니까 방문 가능 여부” 정도로 애매하게 보면 헷갈린다.
이 문제는 값 의미를 명확하게 봐야 한다.
- 0 → 벽
- 1 → 아직 방문 안 한 길
- 2 이상 → 방문했고, 거리 기록됨
BFS가 돌고 나면 의미가 이렇게 바뀐다.
⚠️ 3. 시작점 값이 1이라는 걸 이용한다
문제에서 시작점 (0, 0)은 무조건 1이다.
그래서 BFS를 시작하면 자연스럽게 거리 기준점 역할을 한다.
즉,
- 시작점은 1
- 다음 칸은 2
- 그다음은 3
이런 식으로 누적된다.
그래서 마지막에 graph[n-1][m-1]를 출력하면
그게 곧 최소 칸 수가 된다.
⚠️ 4. 큐는 리스트보다 deque가 맞다
이 문제도 BFS이기 때문에 큐가 필요하다.
리스트의 pop(0)으로도 돌릴 수는 있지만,
앞에서 계속 꺼내는 구조라 deque가 더 맞다.
from collections import deque
q = deque()
q.append((0, 0))
x, y = q.popleft()
이건 이제 그냥 습관처럼 가져가는 게 낫다.
내 시행착오
이 문제에서 내가 처음 헷갈린 건 크게 세 가지였다.
1. 일반 그래프 BFS처럼 보려고 했다
처음엔 BFS라는 건 바로 보였는데,
무의식적으로 “노드 번호” 같은 감각으로 접근했다.
근데 이 문제는 노드가 번호가 아니라 좌표다.
이걸 먼저 바꾸고 들어가야 했다.
2. 방문 배열을 따로 둘까 고민했다
처음엔 visited 배열을 따로 둘까 생각했다.
근데 이 문제는 graph 자체를 거리 기록용으로 덮어쓰는 방식이 더 깔끔했다.
graph[nx][ny] = graph[x][y] + 1
이렇게 하면
- 방문 체크
- 거리 기록
을 동시에 해결할 수 있다.
따로 배열 하나 더 두는 것보다 구조가 단순하다.
3. dx, dy가 처음엔 좀 어색했다
좌표형 문제를 자주 안 풀었을 때는
dx, dy가 괜히 낯설다.
근데 결국 하는 일은 단순하다.
- 위로 한 칸
- 아래로 한 칸
- 왼쪽 한 칸
- 오른쪽 한 칸
이걸 배열로 정리한 것뿐이다.
이걸 익숙하게 가져가야
미로 탐색, 섬 개수, 빙산, 토마토 같은 격자 문제들이 훨씬 쉬워진다.
한 줄 정리
이 문제는 좌표형 BFS의 기본형이다. 핵심은 큐에 좌표를 넣고, graph 자체를 거리 배열처럼 재사용하는 것이다.
'Coding Test > 문제풀이' 카테고리의 다른 글
| 프로그래머스 신고 결과 받기 | 구현 문제에서 문제 해석이 먼저인 이유 (0) | 2026.04.30 |
|---|---|
| 프로그래머스 완주하지 못한 선수 | set 대신 dictionary를 써야 하는 이유 (0) | 2026.04.23 |
| 프로그래머스 파일명 정렬 | 정규식으로 HEAD, NUMBER, TAIL 나누는 방법 (0) | 2026.04.22 |
| 얼음 얼려 먹기 문제 정리 | DFS로 연결 요소 개수 세는 방법 (0) | 2026.04.21 |
| 백준 18352 특정 거리의 도시 찾기 | BFS와 distance 배열 풀이 (0) | 2026.04.15 |
