[프로그래머스] DFS/BFS Lv.3 아이템 줍기

0
post-thumbnail

문제

[level 3] 아이템 줍기 - 87694

문제 링크

성능 요약

메모리: 10.4 MB, 시간: 1.27 ms

구분

코딩테스트 연습 > 깊이/너비 우선 탐색(DFS/BFS)

문제 설명

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

rect_1.png

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

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

rect_2.png

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

rect_4.png

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

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

rect_3.png

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

rect_7.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개인 경우입니다.

입출력 예
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

rect_5.png

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

입출력 예 #2

rect_6.png

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

입출력 예 #3

rect_8.png

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

입출력 예 #4, #5

설명 생략

출처: 프로그래머스 코딩 테스트 연습, https://programmers.co.kr/learn/challenges

접근

이 문제는 그래프를 먼저 그릴 수 있다면 아이템의 위치를 찾는것은 그리 어렵지 않을것 같다는 생각이 들었다.

각각의 직사각형들을 가지고 그래프를 그려야하는데 겹치는 부분을 처리하는것이 관건이었다. 각 사각형들의 테두리만 표기하되 내부 부분은 구별을 해야 경로 탐색을 할 수 있기 때문에 그래프를 우선 그려야 하는 알고리즘을 먼저 생각했다.

for rec in rectangle:
        x1, y1, x2, y2 = rec
        #For Each Cell
        for i in range(x1, x2 + 1):
            for j in range(y1, y2 + 1):
                #if cell is not visited
                if visited[i][j] == False:
                    #mark boundaries
                    if i == x1 or i == x2 or j == y1 or j == y2:
                        graph[i][j] = 1
                        visited[i][j] = True
                    #mark insides
                    else:
                        graph[i][j] = 0
                        visited[i][j] = True
                #cell is visited
                else:
                    #ignore boundaries
                    if i == x1 or i == x2 or j == y1 or j == y2:
                        continue
                    #mark insides nontheless
                    else:
                        graph[i][j] = 0

예제 1의 그래프

각각 칸 마다 테두리 여부와 방문 여부를 따로 구분하였다. 이를 위해 graph 리스트와 visited 리스트 두개가 필요했다. 그래프는 -1로, 비지티드는 False로 초기화 했다.

1차 시도(실패)

간단한 BFS 알고리즘으로 경로를 찾아가면서 거리를 그래프에 업데이트하여 아이템을 발견할 경우 그래프의 값을 리턴하도록 했지만 문제는 경로가 붙어있는 경우였다.

이 경우 테두리가 제대로 그래프에 반영되지 않으면서 의도하지 않은 최단거리가 발생했다.

따라서 그래프의 해상도?를 높일 필요가 있었는데 어떻게 해야 할지 고민하다가
[프로그래머스] 아이템 줍기 을 참고하여 그래프를 포함한 모든 좌표 시스템에 곱2을 하였다.

2차 시도(성공)

그래프의 해상도가 올라감에 따라 테두리가 명확하게 표현될 수 있었다. 그래프의 전체 크기를 2배로 늘릴 뿐만 아니라 시작점과 끝점을 2로 곱하였고 반환 할 때는 2로 나눈 해를 반환시킨다.
탐색 전 그래프

탐색 후 그래프: 우측 하단 35에서 리턴 되었다.

배운 것

  • 그래프의 해상도를 변경하여 탐색을 할 수 있다.
  • BFS를 사용하면 별도의 처리 없이 가장 빨리 리턴 되는 값이 최솟값이된다.

전체 코드 및 결과

from collections import deque

def printGraph(graph: list):
    for gra in graph:
        for g in gra:
            print('%2d' %g, end = ' ')
        print()

def bfs(graph,x1,y1,x2,y2):
    que = deque()
    start = (x1 * 2, y1 * 2)
    visited = set()
    gridSize = len(graph)
    visited.add(start)
    que.append(start)
    dx = [-1,1,0,0]
    dy = [0,0,-1,1]
    
    while que:
        x,y = que.popleft()
        if x == x2 * 2 and y == y2 * 2:
            return graph[x][y] // 2
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            if 0 <= nx < gridSize and 0 <= ny < gridSize and graph[nx][ny] == 1 and (nx,ny) not in visited:
                graph[nx][ny] = graph[x][y] + 1
                que.append((nx,ny))
                visited.add((nx,ny))
    return 0

def solution(rectangle, characterX, characterY, itemX, itemY):
    answer = 0
    gridSize = 102
    #n by n graph
    graph = [[-1 for _ in range(gridSize)] for _ in range(gridSize)]
    visited = [[False for _ in range(gridSize)] for _ in range(gridSize)]
    for rec in rectangle:
        x1, y1, x2, y2 = map(lambda x : x*2, rec)
        #For Each Cell
        for i in range(x1, x2 + 1):
            for j in range(y1, y2 + 1):
                #if cell is not visited
                if visited[i][j] == False:
                    #mark boundaries
                    if i == x1 or i == x2 or j == y1 or j == y2:
                        graph[i][j] = 1
                        visited[i][j] = True
                    #mark insides
                    else:
                        graph[i][j] = 0
                        visited[i][j] = True
                #cell is visited
                else:
                    #ignore boundaries
                    if i == x1 or i == x2 or j == y1 or j == y2:
                        continue
                    #mark insides nontheless
                    else:
                        graph[i][j] = 0
    answer = bfs(graph, characterX, characterY, itemX, itemY)
    return answer

0개의 댓글