알고리즘/프로그래머스

[Java | BFS | Lv. 3] 아이템 줍기

이진지니지니진 2025. 1. 11. 15:24

문제

https://school.programmers.co.kr/learn/courses/30/lessons/87694

 

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

 

지형은 각 변이 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

 

정답코드

import java.util.*;

class Solution {
        private static final int MAX = 102;
        private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

        public int solution(int[][] rectangle, int characterX, int characterY, int itemX, int itemY) {
            boolean[][] border = new boolean[MAX][MAX];
            boolean[][] visited = new boolean[MAX][MAX];

            for (int[] rect : rectangle) {
                int x1 = rect[0] * 2, y1 = rect[1] * 2;
                int x2 = rect[2] * 2, y2 = rect[3] * 2;

                for (int i = x1; i <= x2; i++) {
                    border[i][y1] = true;
                    border[i][y2] = true;
                }
                for (int j = y1; j <= y2; j++) {
                    border[x1][j] = true;
                    border[x2][j] = true;
                }
            }

            for (int[] rect : rectangle) {
                int x1 = rect[0] * 2, y1 = rect[1] * 2;
                int x2 = rect[2] * 2, y2 = rect[3] * 2;

                for (int i = x1 + 1; i < x2; i++) {
                    for (int j = y1 + 1; j < y2; j++) {
                        border[i][j] = false;
                    }
                }
            }

            Queue<int[]> q = new LinkedList<>();
            q.add(new int[]{characterX * 2, characterY * 2, 0});
            visited[characterX * 2][characterY * 2] = true;

            while (!q.isEmpty()) {
                int[] now = q.poll();
                int x = now[0], y = now[1], distance = now[2];

                if (x == itemX * 2 && y == itemY * 2) {
                    return distance / 2;
                }

                for (int[] dir : DIRS) {
                    int nx = x + dir[0];
                    int ny = y + dir[1];

                    if (nx >= 0 && ny >= 0 && nx < MAX && ny < MAX && border[nx][ny] && !visited[nx][ny]) {
                        visited[nx][ny] = true;
                        q.add(new int[]{nx, ny, distance + 1});
                    }
                }
            }

            return -1;
        }
    }

풀이

BFS로 문제를 풀었다!

 

☑️ 좌표 표현하기

- 점은 면으로

x, y 좌표 점은 배열의 면으로 생각하면 편하다

 

- 2배 크기로 늘려

이 포인트는 비슷한 문제를 이전에 풀어본 경험이 있어서 생각할 수 있는 부분이었는데,

점을 면으로 생각해서 문제를 풀 때 경계선과 내부가 모호해지는 점이 생기는데 이를 방지하고자 2배로 늘려 계산하였다!

(이 부분과 관련해서 궁금하다면 직접 찾아보도록!)

 

☑️ 경계선 체크

우선 모든 사각형의 경계선을 표현해주고, 이후 다시 내부는 false를 해준다!

다시 내부인지 체크해서 false 해주는 작업이 필요한 이유는

어떤 사각형에서는 경계선이었는데 다른 사각형과 맞물리면서 내부가 되는 경우가 있기 때문

boolean[][] border = new boolean[MAX][MAX];

for (int[] rect : rectangle) {
	int x1 = rect[0] * 2, y1 = rect[1] * 2;
	int x2 = rect[2] * 2, y2 = rect[3] * 2;

	for (int i = x1; i <= x2; i++) {
		border[i][y1] = true;
		border[i][y2] = true;
	}
    
	for (int j = y1; j <= y2; j++) {
		border[x1][j] = true;
		border[x2][j] = true;
	}
}

for (int[] rect : rectangle) {
	int x1 = rect[0] * 2, y1 = rect[1] * 2;
	int x2 = rect[2] * 2, y2 = rect[3] * 2;

	for (int i = x1 + 1; i < x2; i++) {
		for (int j = y1 + 1; j < y2; j++) {
			border[i][j] = false;
		}
	}
}

 

 

🚨 헤맸던 Point

- 경계선 체크

어떤 사각형에서는 경계선이었는데 다른 사각형일 때는 내부가 되는 경우를 고려하지 않았다

그래서 경계선 체크만 하고 추후에 다시 확인해서 false를 만들어주지 않아 틀렸었다