[프로그래머스 level 3] 아이템 줍기

Published2024.07.13
Read Time8 min read

[level 3] 아이템 줍기 (BFS/DFS)

문제 설명

다음과 같은 다각형 모양 지형에서 캐릭터가 아이템을 줍기 위해 이동하려 합니다.

1.png

지형은 각 변이 x축, y축과 평행한 직사각형이 겹쳐진 형태로 표현하며, 캐릭터는 이 다각형의 둘레(굵은 선)를 따라서 이동합니다.

만약 직사각형을 겹친 후 다음과 같이 중앙에 빈 공간이 생기는 경우, 다각형의 가장 바깥쪽 테두리가 캐릭터의 이동 경로가 됩니다.

2.png

단, 서로 다른 두 직사각형의 x축 좌표 또는 y축 좌표가 같은 경우는 없습니다.

rect_3.png

즉, 위 그림처럼 서로 다른 두 직사각형이 꼭짓점에서 만나거나, 변이 겹치는 경우 등은 없습니다.

다음 그림과 같이 지형이 2개 이상으로 분리된 경우도 없습니다.

rect_4.png

한 직사각형이 다른 직사각형 안에 완전히 포함되는 경우 또한 없습니다.

rect_5.png

지형을 나타내는 직사각형이 담긴 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개인 경우입니다.


입출력 예

rectanglecharacterXcharacterYitemXitemYresult
[[1,1,7,4],[3,2,5,5],[4,3,6,9],[2,6,8,8]]137817
[[1,1,8,4],[2,2,4,9],[3,6,9,8],[6,3,7,7]]976111
[[1,1,5,7]]11479
[[2,1,7,5],[6,4,10,10]]3171015
[[2,2,5,5],[1,3,6,4],[3,1,4,6]]146310
입출력 예 설명

입출력 예 #1

rect_5.png

캐릭터 위치는 (1, 3)이며, 아이템 위치는 (7, 8)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.

입출력 예 #2

rect_7.png

캐릭터 위치는 (9, 7)이며, 아이템 위치는 (6, 1)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.

입출력 예 #3

[rect_8.png]

캐릭터 위치는 (1, 1)이며, 아이템 위치는 (4, 7)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.

구현 방법

전체 직사각형들을 합친 것의 바깥쪽 테두리만 돌게하려면 어떻게 하는가?

  • rectangle을 순회하면서 범위 중 직사각형 내부는 0으로 테두리는 1로 board값을 갱신한다.
  • 이 때, 만일 다른 직사각형의 내부안에 현재 직사각형의 테두리가 있지 않을 경우에만 board를 1로 갱신한다.

예외 사항

exception.png 좌표가 인접한 경우 위와 같이 의도하지 않은 경우를 초래할 수도 있다.

따라서 이를 해결하기 위해 모든 좌표의 값을 2배씩 해주어야 한다.

이를 모두 반영하여 board 테이블을 만들면,

board = [[-1 for _ in range(102)] for _ in range(102)];
for r in rectangle:
	# map 객체를 통해 모든 좌표값에 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):
	        # 직사각형 내부의 경우 0으로 개신
            if x1<i<x2 and y1<j<y2:
               board[i][j] = 0;
            # 현재 직사각형의 내부가 아니면서 다른 직사각형의 내부 역시 아닐 때
            elif board[i][j] != 0:
               board[i][j] = 1;

이제 여기서 bfs를 이용하여 캐릭터 위치에서 아이템 위치까지의 최단거리를 구하면 된다.

전체 코드

from collections import deque

def solution(rectangle, characterX, characterY, itemX, itemY):
	board = [[-1 for _ in range(102)] for _ in range(102)];
	for r in rectangle:
		# map 객체를 통해 모든 좌표값에 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):
		        # 직사각형 내부의 경우 0으로 개신
	            if x1<i<x2 and y1<j<y2:
	               board[i][j] = 0;
	            # 현재 직사각형의 내부가 아니면서 다른 직사각형의 내부 역시 아닐 때
	            elif board[i][j] != 0:
	               board[i][j] = 1;
    dir = [(1,0),(0,1),(-1,0),(0,-1)];
	visited = [[0 for _ in range(102)] for _ in range(102)];
    queue = deque();
    queue.append((characterX*2,characterY*2));
    visited[characterX*2][characterY*2] = 1;
    while queue:
        x,y = queue.popleft();
        for dx,dy in dir:
            nx,ny = x+dx,y+dy;
            if 0<=nx<102 and 0<=ny<102:
	            # 직사각형의 테두리이면서 아직 방문하지 않았을 때
                if board[nx][ny] == 1 and visited[nx][ny] == 0:
                    queue.append((nx,ny));
                    # 거리 갱신
                    visited[nx][ny] = visited[x][y] + 1;
    return visited[itemX*2][itemY*2]//2 

느낀 점

직사각형의 내부와 테두리를 갱신하는 방법은 어떻게 떠올렸지만, 예외 상항과 그에 대한 해결 방법은 떠올리지는 못했다. 예외 처리에 관한 관점을 조금 더 넓혀나갈 수 있던 문제였다.