[DFS/BFS] 아이템 줍기
https://school.programmers.co.kr/learn/courses/30/lessons/87694
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
문제 설명
다음과 같은 다각형 모양 지형에서 캐릭터가 아이템을 줍기 위해 이동하려 합니다.
지형은 각 변이 x축, y축과 평행한 직사각형이 겹쳐진 형태로 표현하며, 캐릭터는 이 다각형의 둘레(굵은 선)를 따라서 이동합니다.
만약 직사각형을 겹친 후 다음과 같이 중앙에 빈 공간이 생기는 경우, 다각형의 가장 바깥쪽 테두리가 캐릭터의 이동 경로가 됩니다.
단, 서로 다른 두 직사각형의 x축 좌표 또는 y축 좌표가 같은 경우는 없습니다.
즉, 위 그림처럼 서로 다른 두 직사각형이 꼭짓점에서 만나거나, 변이 겹치는 경우 등은 없습니다.
다음 그림과 같이 지형이 2개 이상으로 분리된 경우도 없습니다.
한 직사각형이 다른 직사각형 안에 완전히 포함되는 경우 또한 없습니다.
지형을 나타내는 직사각형이 담긴 2차원 배열 rectangle, 초기 캐릭터의 위치 characterX, characterY, 아이템의 위치 itemX, itemY가 solution 함수의 매개변수로 주어질 때, 캐릭터가 아이템을 줍기 위해 이동해야 하는 가장 짧은 거리를 return 하도록 solution 함수를 완성해주세요.
제한사항
- rectangle의 세로(행) 길이는 1 이상 4 이하입니다.
- rectangle의 원소는 각 직사각형의 [좌측 하단 x, 좌측 하단 y, 우측 상단 x, 우측 상단 y] 좌표 형태입니다.
- 직사각형을 나타내는 모든 좌표값은 1 이상 50 이하인 자연수입니다.
- 서로 다른 두 직사각형의 x축 좌표, 혹은 y축 좌표가 같은 경우는 없습니다.
- 문제에 주어진 조건에 맞는 직사각형만 입력으로 주어집니다.
- charcterX, charcterY는 1 이상 50 이하인 자연수입니다.
- 지형을 나타내는 다각형 테두리 위의 한 점이 주어집니다.
- itemX, itemY는 1 이상 50 이하인 자연수입니다.
- 지형을 나타내는 다각형 테두리 위의 한 점이 주어집니다.
- 캐릭터와 아이템의 처음 위치가 같은 경우는 없습니다.
- 전체 배점의 50%는 직사각형이 1개인 경우입니다.
- 전체 배점의 25%는 직사각형이 2개인 경우입니다.
- 전체 배점의 25%는 직사각형이 3개 또는 4개인 경우입니다.
입출력 예
rectangle | characterX | characterY | itemX | itemY | result |
[[1,1,7,4],[3,2,5,5],[4,3,6,9],[2,6,8,8]] | 1 | 3 | 7 | 8 | 17 |
[[1,1,8,4],[2,2,4,9],[3,6,9,8],[6,3,7,7]] | 9 | 7 | 6 | 1 | 11 |
[[1,1,5,7]] | 1 | 1 | 4 | 7 | 9 |
[[2,1,7,5],[6,4,10,10]] | 3 | 1 | 7 | 10 | 15 |
[[2,2,5,5],[1,3,6,4],[3,1,4,6]] | 1 | 4 | 6 | 3 | 10 |
입출력 예 설명
입출력 예 #1
- 캐릭터 위치는 (1, 3)이며, 아이템 위치는 (7, 8)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.
입출력 예 #2
- 캐릭터 위치는 (9, 7)이며, 아이템 위치는 (6, 1)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.
입출력 예 #3
- 캐릭터 위치는 (1, 1)이며, 아이템 위치는 (4, 7)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.
입출력 예 #4, #5
- 설명 생략
BFS 알고리즘을 푸는 게 어려운 문제는 아닙니다. 이전에 풀었던 문제들과 동일 유형이죠.
다만 여기서 가장 큰 문제가 되는 건 좌표를 2차원으로 변환하는 과정입니다. 이 문제는 좌표를 변환하는 것 때문에 레벨3으로 판정되었다고 해도 과장이 아닙니다. 그만큼 생각할 게 있는 문제입니다.
왜 이런 문제가 발생하느냐라고 생각하실 수 있습니다. 그냥 그대로 좌표를 옮기면 되지 않을까 싶지만, 그러면 문제가 발생합니다.
이건 1번 예시를 그림으로 표시한 예시입니다. 제가 따로 표시해놓은 부분이 보이실 겁니다.
이 부분은 좌표 상에는 문제가 없었지만, 실제로 배열로 옮기는 과정에서 이어지지 말아야 할 부분이 이어진 경우입니다.
좌표 상에서는 ㄷ이 배열 상으로는 ㅁ으로 처리된다는 거죠.
이 때문에 원래라면 (3, 5)에서 (4, 5)으로 넘어가야 하는데, 배열 상으로는 이어져 있기 때문에 (3, 6)으로 점프할 수 있는 문제가 발생합니다.
이 문제의 해결방법은 간단합니다. 그냥 스케일을 2배로 늘리면 됩니다.
어차피 BFS와 DFS는 최단 거리를 보장하기 때문에 맨 마지막에 2로 나눠주면 동일한 거리를 보장할 수 있습니다.
저는 이 부분에서 좀 고민하느라 시간을 많이 썼습니다. 점은 그대로 두고 선분만 늘리면 될까? 하면서 고민했는데, 그냥 스케일을 2배로 늘려도 아무 문제 없습니다.
그 다음은 이 테두리를 표시하는 부분입니다.
이 문제에서 겹치는 부분이 생기는데, 겹치는 부분에 있는 테두리들은 전부 사라지고 내부로 처리되어야 합니다.
테두리를 먼저 설정하고 내부로 처리하는 로직보다는 먼저 테두리 안의 내부를 처리하고, 처리된 내부를 감싸는 형식으로 테두리를 설정하면 됩니다.
마지막은 BFS 알고리즘입니다. 이 부분은 이전 문제와 풀이 방법이 동일합니다.
다만 우리가 좌표를 2배로 늘려놨기 때문에, 원래대로 되돌리는 작업만 잊지 않으면 됩니다.
from collections import deque
def solution(rectangle, characterX, characterY, itemX, itemY):
answer = 0
# 2차원 맵
maps = [[-1 for i in range(102)] for i in range(102)]
# 직사각형 설정
for r in rectangle:
# ㄷ이 ㅁ으로 인식되는 경우를 막기 위해 2배 처리
x1, y1, x2, y2 = map(lambda x:x * 2, r)
# 테두리 설정
for i in range(x1, x2+1):
for j in range(y1, y2+1):
# x1, x2, y1, y2는 테두리이므로 제외하고 내부만 0으로 채움
if x1 < i < x2 and y1 < j < y2:
maps[i][j] = 0
# 내부를 감싼 부분은 테두리가 된다.
elif maps[i][j] != 0:
maps[i][j] = 1
# 설정이 끝나면 외부 -1, 내부 0, 테두리 1
# 좌표도 2배 처리
cX, cY, iX, iY = 2 * characterX, 2 * characterY, 2 * itemX, 2 * itemY
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
# bfs 알고리즘
queue = deque([(cX, cY, 1)])
while queue:
currentX, currentY, dist = queue.popleft()
if currentX == iX and currentY == iY:
return dist // 2
maps[currentX][currentY] = 0 # 접근 불가 처리
# 4방향 체크
for d in directions:
dx, dy = d
tx, ty = currentX + dx, currentY + dy
# 인덱스 범주 내 + 테두리인 부분만 탐색
if 0 <= tx < 102 and 0 <= ty < 102 and maps[tx][ty] == 1:
maps[tx][ty] = 0
queue.append([tx, ty, dist + 1])
return -1
내부 설정과 테두리 쪽은 인터넷으로 참조하였습니다.
검색은 최대한 줄이려고는 하지만 너무 어렵네요.