문제 링크 : https://school.programmers.co.kr/learn/courses/30/lessons/154540
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
이번 포스팅에서는 프로그래머스의 Level 2 문제인 무인도 여행 문제를 DFS(깊이 우선 탐색) 으로 해결한 방법을 소개하려고 합니다.
BFS보다는 아직 DFS가 더 익숙해서 DFS로 풀었는데요, DFS는 푸는 패턴이 있기 때문에 그 패턴을 익히고 나면 문제에 따라 쉽게 접근할 수 있습니다.
이 포스팅을 통해 문제를 어떻게 접근하고 해결했는지, 그리고 문제를 해결하며 배운 점들을 공유하겠습니다.
문제 개요
이 문제의 핵심은 결국 같은 묶음의 원소들을 찾아 누적 합을 구하는 것입니다.
문제에서 예시로 주어진 지도처럼 `{5, 9, 1, 1, 5, 2, 3, 1}`, `{1}`, `{1}` 각각 묶음을 찾아 누적 값을 구해야 합니다.
접근 방법
저는 이런 식으로 접근하였습니다.
1. 파라미터로 전달받는 `maps`를 2차원 배열인 `graph`로 연결한다. (`X`표시가 아닐 경우에만 `graph`에 넣음.)
2. `graph`의 각 `[i][j]`를 방문한 적이 있는지 확인하기 위해 같은 크기의 `visited`라는 2차원 배열을 만든다.
3. 각 노드에서 `DFS`를 실행하여 현재 인덱스의 상, 하, 좌, 우를 확인하고 연결이 되어 있을 경우에는 `DFS`를 다시 호출하여 `sum`에 값을 더한다.
4. 모든 묶음의 값을 출력
알게된 내용
1. 패딩(Padding)
graph = new int[M + 2][N + 2];
visited = new boolean[M + 2][N + 2];
처음 코드를 작성하였을 때는 `maps.length`, `maps[0].length()` 만큼의 길이를 제한하였는데, 인덱스 범위 오류(Index Out of Bounds)가 발생해 이를 방지하고자 +2의 여유를 주었습니다. 이를 패딩 기법이라 하는데요. 패딩을 사용하는 주된 목적은 경계 조건 검사를 단순화 하는 것입니다. 여유 있게 `graph`를 만들어 실제 데이터를 `graph[1][1]`부터 `graph[M][N]`까지 채웁니다.
맨 첫 줄에 있는 요소들은 빈 공간으로 채워지는 것이 아니라 int 자료형 초기값인 0으로 자동 초기화 될 것입니다.
2. char -> int 파싱은 Character.getNumbericValue()를 사용
char c = maps[i].charAt(j);
if (c != 'X') {
graph[i + 1][j + 1] = Character.getNumericValue(c);
}
`char`에서 int로 파싱하기 위해서는 `String.valueOf `-> `Integer.parseInt` 총 2번의 파싱의 과정을 거쳐야 한다고 생각했습니다.
찾아보니 `char`에서 `int`로 파싱할 때는 `Character.getNumericValue()`를 사용해 쉽게 파싱할 수 있었습니다.
그리고 `maps[i].charAt(j)` 또한 굳이 `String`으로 파싱하여 `equals()`를 사용해 비교하지 않고 `char` 타입이기 때문에 `==` 을 사용해서 비교할 수 있다는 점도 깨달았습니다.
3. DFS 실행 범위 조정
// 3. dfs 실행
for (int i = 1; i <= M; i++) {
for (int j = 1; j <= N; j++) {
if (graph[i][j] != 0 && !visited[i][j]) {
dfs(i, j);
answer.add(sum);
sum = 0;
}
}
}
처음 작성한 코드에서는 패딩을 사용하지 않았기 때문에 모든 좌표를 0부터 시작해서 탐색했습니다. 이렇게 할 경우, DFS를 실행하면서 상하좌우를 탐색할 때 항상 경계 바깥으로 넘어가는 상황을 검사해야 합니다. 인덱스 범위 오류(Index Out of Bounds)가 발생할 수 있기 때문입니다.
그래서 다음과 같은 조건문을 사용해 경계 검사를 했습니다.
if (newY >= 0 && newY < rows && newX >= 0 && newX < cols && graph[newY][newX] != 0 && !visited[newY][newX]) {
dfs(newY, newX);
}
이 조건에서 `newY >= 0 && newY < rows && newX >= 0 && newX < cols`는 탐색하려는 좌표가 그래프의 유효한 범위 안에 있는지 검사하는 부분입니다. 이 조건이 없으면 `newY`나 `newX`가 배열의 범위를 벗어나면서 인덱스 범위 오류(Index Out of Bounds) 예외가 발생할 수 있습니다.
하지만 패딩을 사용하면 인덱스가 그래프의 유효 범위를 넘어갈 일이 없어집니다. 예를 들어, 탐색 중 `newY`나 `newX`가 `-1`이 되거나 `M` 또는 `N`보다 큰 값이 되더라도, 이 값들은 패딩으로 설정된 `0` 값을 가지는 가장자리에 접근하게 됩니다. 가장자리는 항상 `0`으로 채워져 있기 때문에, `graph[newY][newX] != 0` 조건에서 필터링되기 때문입니다.
따라서, 경계를 검사하는 조건식을 추가할 필요가 없어져 아래와 같이 분기문을 간소화할 수 있습니다.
if (graph[newY][newX] != 0 && !visited[newY][newX]) {
dfs(newY, newX);
}
최종 코드
import java.util.ArrayList;
import java.util.Collections;
class Solution {
static int[][] graph;
static boolean[][] visited;
static int sum, M, N;
static ArrayList<Integer> answer = new ArrayList<>();
static int dirX[] = { 0, 0, -1, 1 };
static int dirY[] = { -1, 1, 0, 0 };
static void dfs(int y, int x) {
visited[y][x] = true;
sum += graph[y][x];
for (int i = 0; i < 4; i++) {
int newY = y + dirY[i];
int newX = x + dirX[i];
if (graph[newY][newX] != 0 && !visited[newY][newX]) {
dfs(newY, newX);
}
}
}
public ArrayList<Integer> solution(String[] maps) {
M = maps.length;
N = maps[0].length();
graph = new int[M + 2][N + 2];
visited = new boolean[M + 2][N + 2];
// 1. graph 연결
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
char c = maps[i].charAt(j);
if (c != 'X') {
graph[i + 1][j + 1] = Character.getNumericValue(c);
}
}
}
// 2. dfs 실행
for (int i = 1; i <= M; i++) {
for (int j = 1; j <= N; j++) {
if (graph[i][j] != 0 && !visited[i][j]) {
dfs(i, j);
answer.add(sum);
sum = 0;
}
}
}
// 3. 출력
if (answer.isEmpty()) {
answer.add(-1);
} else {
Collections.sort(answer);
}
return answer;
}
}
마치며
이번 문제를 통해 DFS에 대해 다시 한 번 익힐 수 있었습니다.
DFS 문제의 경우 패턴이 정해져 있기 때문에 DFS보다 SUM을 어느 위치에 두어야 하는지를 파악해야 하는 문제였다고 생각합니다. 이후에는 그림을 그려 왜 해당 위치에 SUM을 두어야 하는지 이해하는 시간도 필요하다 생각하였습니다.
'🤖 Algorithm' 카테고리의 다른 글
[백준 / Java] 1715번 카드 정렬하기 (0) | 2024.10.05 |
---|---|
[백준 / Java] 1987번 알파벳 (2) | 2024.10.04 |