Open SDeung01 opened 11 months ago
메리는 여름을 맞아 무인도로 여행을 가기 위해 지도를 보고 있습니다. 지도에는 바다와 무인도들에 대한 정보가 표시돼 있습니다. 지도는 1 x 1크기의 사각형들로 이루어진 직사각형 격자 형태이며, 격자의 각 칸에는 'X' 또는 1에서 9 사이의 자연수가 적혀있습니다. 지도의 'X'는 바다를 나타내며, 숫자는 무인도를 나타냅니다. 이때, 상, 하, 좌, 우로 연결되는 땅들은 하나의 무인도를 이룹니다. 지도의 각 칸에 적힌 숫자는 식량을 나타내는데, 상, 하, 좌, 우로 연결되는 칸에 적힌 숫자를 모두 합한 값은 해당 무인도에서 최대 며칠동안 머물 수 있는지를 나타냅니다. 어떤 섬으로 놀러 갈지 못 정한 메리는 우선 각 섬에서 최대 며칠씩 머물 수 있는지 알아본 후 놀러갈 섬을 결정하려 합니다.
지도를 나타내는 문자열 배열 maps가 매개변수로 주어질 때, 각 섬에서 최대 며칠씩 머무를 수 있는지 배열에 오름차순으로 담아 return 하는 solution 함수를 완성해주세요. 만약 지낼 수 있는 무인도가 없다면 -1을 배열에 담아 return 해주세요.
제한사항
maps | result |
---|---|
["X591X","X1X5X","X231X", "1XXX1"] | [1, 1, 27] |
["XXX","XXX","XXX"] | [-1] |
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
class Solution {
// 위치 정보를 가지는 내부 클래스
class Pos{
private int x;
private int y;
public Pos(int x, int y) {
this.x = x;
this.y = y;
}
}
public int[] solution(String[] maps) {
int[][] newMap = newMap(maps);
int xLength = maps[0].length();
int yLength = maps.length;
int[] aroundX = {0, 0, -1, 1};
int[] aroundY = {-1, 1, 0, 0};
boolean[][] visited = new boolean[yLength][xLength];
Queue<Pos> posQ = new LinkedList<>();
// BFS로 탐색을 완료한 섬 하나의 음식 총량을 List에 담는다.
ArrayList<Integer> islandList = new ArrayList<>();
// [0,0] 좌표부터 탐색 시작점을 옮겨가며 BFS를 반복한다.
for(int y = 0; y < yLength; y++){
for (int x = 0; x < xLength; x++){
int sum = 0;
// 탐색시작점이 바다가 아니고 아직 탐색하지 않은 장소라면 BFS 시작
if(newMap[y][x] > 0 && !visited[y][x]){
// 시작지점 세팅
// 미리 큐에 담은 Pos가 없고, 탐색 시 큐를 모두 비울때까지 탐색하므로 시작시 큐는 항상 비어있다.
if(posQ.isEmpty()){
posQ.offer(new Pos(x,y));
visited[y][x] = true;
sum += newMap[y][x];
}
// BFS
while(!posQ.isEmpty()) {
Pos pos = posQ.poll();
for(int i = 0; i < 4; i++){
int expX = pos.x + aroundX[i];
int expY = pos.y + aroundY[i];
if(expX >= 0 && expX < xLength && expY >= 0 && expY < yLength){
if(newMap[expY][expX] == 0 || visited[expY][expX]) continue;
visited[expY][expX] = true;
sum += newMap[expY][expX]; // 탐색 중 발견한 음식의 양을 더함
posQ.offer(new Pos(expX, expY));
}
}
} islandList.add(sum);
}
}
}
Collections.sort(islandList);
int[] result = islandList.stream().mapToInt(i->i).toArray();
return islandList.isEmpty() ? new int[] {-1} : result;
}
// String 배열 형태의 maps를 문자 단위로 쪼갠 후 숫자로 변환하여 int 2차원 배열의 형태로 반환
// 음식(숫자)은 1~9까지이므로 음식이 없는 위치(바다, X)는 0으로 저장
private int[][] newMap(String[] maps){
int[][] newMap = new int[maps.length][maps[0].length()];
for(int y = 0; y < maps.length; y++){
for(int x = 0; x < maps[0].length(); x++){
char food = maps[y].charAt(x);
if(Character.isDigit(food)){
// 제한조건에 위배되지 않으므로 편의를 위해 .isDigit()을 사용하였으나
// 예상 못한 오류를 피하려면 1~9까지의 범위를 명확히 주는 것이 좋다.
newMap[y][x] = food - '0';
}else {
newMap[y][x] = 0;
}
}
} return newMap;
}
}
import java.util.ArrayList;
import java.util.Arrays;
class Solution {
static int[] dI = {1,0,-1,0};
static int[] dJ = {0,-1,0,1};
static ArrayList<Integer> arr = new ArrayList<>();
public int[] solution(String[] maps) {
int[][] map = new int[maps.length+2][maps[0].length()+2];
Boolean[][] visit = new Boolean[maps.length+2][maps[0].length()+2];
for(int[] temp: map) {
Arrays.fill(temp, 0);
}
for(Boolean[] temp: visit) {
Arrays.fill(temp, true);
}
Boolean flag =false;
for(int i = 1; i <= maps.length; i++) {
for(int j = 1; j <= maps[0].length(); j++) {
if(maps[i-1].charAt(j-1) == 'X') {
map[i][j] = 0;
}
else {
map[i][j] = maps[i-1].charAt(j-1) - '0';
visit[i][j] = false;
flag = true;
}
}
}
if(!flag) {
return new int[] {-1};
}
for(int i = 1; i <= maps.length; i++) {
for(int j = 1; j <= maps[0].length(); j++) {
if(map[i][j] > 0 && !visit[i][j]) {
DFS(visit, map, i, j,0);
}
}
}
int ans[] = new int[arr.size()];
for(int i = 0; i < arr.size(); i++) {
ans[i] = arr.get(i);
}
Arrays.sort(ans);
return ans;
}
int DFS(Boolean[][] visit, int[][] map, int i, int j, int depth) {
if(visit[i][j] || map[i][j] == 0) {
return 0;
}
visit[i][j] = true;
int ans = map[i][j];
for(int k = 0; k < 4; k++) {
ans += DFS(visit,map,i+dI[k],j+dJ[k], depth+1);
}
if(depth == 0) {
arr.add(ans);
}
return ans;
}
}
1 x 1 크기의 칸들로 이루어진 직사각형 격자 형태의 미로에서 탈출하려고 합니다. 각 칸은 통로 또는 벽으로 구성되어 있으며, 벽으로 된 칸은 지나갈 수 없고 통로로 된 칸으로만 이동할 수 있습니다. 통로들 중 한 칸에는 미로를 빠져나가는 문이 있는데, 이 문은 레버를 당겨서만 열 수 있습니다. 레버 또한 통로들 중 한 칸에 있습니다. 따라서, 출발 지점에서 먼저 레버가 있는 칸으로 이동하여 레버를 당긴 후 미로를 빠져나가는 문이 있는 칸으로 이동하면 됩니다. 이때 아직 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있습니다. 미로에서 한 칸을 이동하는데 1초가 걸린다고 할 때, 최대한 빠르게 미로를 빠져나가는데 걸리는 시간을 구하려 합니다.
미로를 나타낸 문자열 배열 maps가 매개변수로 주어질 때, 미로를 탈출하는데 필요한 최소 시간을 return 하는 solution 함수를 완성해주세요. 만약, 탈출할 수 없다면 -1을 return 해주세요.
제한사항
maps | result |
---|---|
["SOOOL","XXXXO","OOOOO","OXXXX","OOOOE"] | 16 |
["LOOXS","OOOOX","OOOOO","OOOOO","EOOOO"] | -1 |
import java.util.LinkedList;
import java.util.Queue;
class Solution {
class Point{
private final int x;
private final int y;
private final int sec;
public Point(int x, int y, int sec) {
this.x = x;
this.y = y;
this.sec = sec;
}
}
public int solution(String[] maps) {
// BFS 탐색을 위한 기본 세팅
int xLength = maps[0].length();
int yLength = maps.length;
boolean[][] visited = new boolean[yLength][xLength];
Queue<Point> roadQ = new LinkedList<>();
int[][] direction = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // x,y 상하좌우
// 시작 지점을 탐색하여 roadQ와 visited에 시작지점의 위치를 세팅
int[] start = findStart(maps);
roadQ.offer(new Point(start[0], start[1], 0));
visited[start[1]][start[0]] = true;
// 레버 위치까지 탐색하고 만약 레버에 도착하지 못했다면 -1 리턴
char targetL = 'L';
Point leverPoint = goToPoint(roadQ, maps, visited, direction, xLength, yLength, targetL);
if(leverPoint == null) return -1;
// 새 탐색을 위해 roadQ에 저장된 값을 모두 제거
// visited를 새로 초기화 (지나온 길을 다시 지나갈 수 있음)
roadQ.clear();
visited = new boolean[yLength][xLength];
// 출발 위치(레버)를 세팅
roadQ.offer(leverPoint);
visited[leverPoint.y][leverPoint.x] = true;
// 출구 위치까지 탐색하고 만약 출구에 도착하지 못했다면 -1 리턴
char targetE = 'E';
Point exitPoint = goToPoint(roadQ, maps, visited, direction, xLength, yLength, targetE);
if(exitPoint == null) return -1;
return exitPoint.sec;
}
// 시작 지점 찾기
private int[] findStart(String[] maps){
for(int y = 0; y < maps.length; y++){
for(int x = 0; x < maps[0].length(); x++){
if(maps[y].charAt(x) == 'S') return new int[] {x, y};
}
} return new int[] {0, 0};
}
// 시작지점(혹은 레버 위치)에서 타겟 지점(레버, 츌구)까지 최단거리 탐색을 위한 BFS
// 타겟 지점의 위치와 이동 시간을 포함하는 Point 클래스 반환
private Point goToPoint(Queue<Point> roadQ, String[] maps, boolean[][] visited, int[][] direction, int xLength, int yLength, char target){
while(!roadQ.isEmpty()){
Point road = roadQ.poll();
int sec = road.sec;
for(int[] dir : direction){
int x = road.x + dir[0];
int y = road.y + dir[1];
if(x >= 0 && x < xLength && y >= 0 && y < yLength){
if(visited[y][x] || maps[y].charAt(x) == 'X') continue;
if(maps[y].charAt(x) == target) return new Point(x,y,sec+1);
visited[y][x] = true;
roadQ.offer(new Point(x,y,sec+1));
}
}
} return null;
}
}
## 회고
- 풀이 후 조금 아쉬웠던 부분을 리팩토링 해보았다.
- `findStart` 메소드의 리턴타입을 Point로 변경하고 관련 부분을 수정하였다.
- `goToPoint` 메소드의 파라매터가 너무 구구절절 길어져서 이부분을 조금 고쳐보았다.
- 탐색지의 상하좌우 방향을 나타낼 배열 direction은 어차피 해당 메소드 내에서만 사용되므로 메소드 안에서 지역변수 선언한다.
- 이미 maps가 파라매터로 들어가므로 굳이 그 길이를 나타내는 xLength, yLength를 파라매터로 줄 필요는 없다. 다만 이미 선언한 변수를 메소드 안에서 다시 선언해야 하는게 조금 걸린다.
```java
import java.util.LinkedList;
import java.util.Queue;
class Solution {
class Point{
private final int x;
private final int y;
private final int sec;
public Point(int x, int y, int sec) {
this.x = x;
this.y = y;
this.sec = sec;
}
}
public int solution(String[] maps) {
// BFS 탐색을 위한 기본 세팅
int xLength = maps[0].length();
int yLength = maps.length;
boolean[][] visited = new boolean[yLength][xLength];
Queue<Point> roadQ = new LinkedList<>();
// 시작 지점을 탐색하여 roadQ와 visited에 시작지점의 위치를 세팅
Point startPoint = findStart(maps);
roadQ.offer(startPoint);
visited[startPoint.y][startPoint.x] = true;
// 레버 위치까지 탐색하고 만약 레버에 도착하지 못했다면 -1 리턴
char targetL = 'L';
Point leverPoint = goToPoint(roadQ, maps, visited, targetL);
if(leverPoint == null) return -1;
// 새 탐색을 위해 roadQ에 저장된 값을 모두 제거
// visited를 새로 초기화 (지나온 길을 다시 지나갈 수 있음)
roadQ.clear();
visited = new boolean[yLength][xLength];
// 출발 위치(레버)를 세팅
roadQ.offer(leverPoint);
visited[leverPoint.y][leverPoint.x] = true;
// 출구 위치까지 탐색하고 만약 출구에 도착하지 못했다면 -1 리턴
char targetE = 'E';
Point exitPoint = goToPoint(roadQ, maps, visited, targetE);
if(exitPoint == null) return -1;
return exitPoint.sec;
}
// 시작 지점 찾기
private Point findStart(String[] maps){
for(int y = 0; y < maps.length; y++){
for(int x = 0; x < maps[0].length(); x++){
if(maps[y].charAt(x) == 'S') return new Point(x,y,0);
}
} return null;
}
// 시작지점(혹은 레버 위치)에서 타겟 지점(레버, 츌구)까지 최단거리 탐색을 위한 BFS
// 타겟 지점의 위치와 이동 시간을 포함하는 Point 클래스 반환
private Point goToPoint(Queue<Point> roadQ, String[] maps, boolean[][] visited, char target){
int[][] direction = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; // x,y 상하좌우
int xLength = maps[0].length();
int yLength = maps.length;
while(!roadQ.isEmpty()){
Point road = roadQ.poll();
int sec = road.sec;
for(int[] dir : direction){
int x = road.x + dir[0];
int y = road.y + dir[1];
if(x >= 0 && x < xLength && y >= 0 && y < yLength){
if(visited[y][x] || maps[y].charAt(x) == 'X') continue;
if(maps[y].charAt(x) == target) return new Point(x,y,sec+1);
visited[y][x] = true;
roadQ.offer(new Point(x,y,sec+1));
}
}
} return null;
}
}
문제 1 : 게임 맵 최단거리
이번에는 탐색 알고리즘 문제 중에서 BFS 위주로 풀어보았습니다. 알고리즘 설명을 보아도 도저히 구현하는 방법을 모르겠어서 다른 사람의 풀이를 참고한 후 일부 변형하여 다시 푼 문제입니다.
문제
ROR 게임은 두 팀으로 나누어서 진행하며, 상대 팀 진영을 먼저 파괴하면 이기는 게임입니다. 따라서, 각 팀은 상대 팀 진영에 최대한 빨리 도착하는 것이 유리합니다.
지금부터 당신은 한 팀의 팀원이 되어 게임을 진행하려고 합니다. 다음은 5 x 5 크기의 맵에, 당신의 캐릭터가 (행: 1, 열: 1) 위치에 있고, 상대 팀 진영은 (행: 5, 열: 5) 위치에 있는 경우의 예시입니다.
위 그림에서 검은색 부분은 벽으로 막혀있어 갈 수 없는 길이며, 흰색 부분은 갈 수 있는 길입니다. 캐릭터가 움직일 때는 동, 서, 남, 북 방향으로 한 칸씩 이동하며, 게임 맵을 벗어난 길은 갈 수 없습니다. 아래 예시는 캐릭터가 상대 팀 진영으로 가는 두 가지 방법을 나타내고 있습니다.
위 예시에서는 첫 번째 방법보다 더 빠르게 상대팀 진영에 도착하는 방법은 없으므로, 이 방법이 상대 팀 진영으로 가는 가장 빠른 방법입니다.
만약, 상대 팀이 자신의 팀 진영 주위에 벽을 세워두었다면 상대 팀 진영에 도착하지 못할 수도 있습니다. 예를 들어, 다음과 같은 경우에 당신의 캐릭터는 상대 팀 진영에 도착할 수 없습니다.
게임 맵의 상태 maps가 매개변수로 주어질 때, 캐릭터가 상대 팀 진영에 도착하기 위해서 지나가야 하는 칸의 개수의 최솟값을 return 하도록 solution 함수를 완성해주세요. 단, 상대 팀 진영에 도착할 수 없을 때는 -1을 return 해주세요.
입출력 예
문제 풀이
회고
class Solution {
}