[프로그래머스] 미로 탈출 Javascript (lv2)

Taemin Jang·2023년 2월 17일
0

코딩테스트

목록 보기
7/9
post-thumbnail

[프로그래머스] 미로 탈출

문제 설명

1 x 1 크기의 칸들로 이루어진 직사각형 격자 형태의 미로에서 탈출하려고 합니다. 각 칸은 통로 또는 벽으로 구성되어 있으며, 벽으로 된 칸은 지나갈 수 없고 통로로 된 칸으로만 이동할 수 있습니다. 통로들 중 한 칸에는 미로를 빠져나가는 문이 있는데, 이 문은 레버를 당겨서만 열 수 있습니다. 레버 또한 통로들 중 한 칸에 있습니다. 따라서, 출발 지점에서 먼저 레버가 있는 칸으로 이동하여 레버를 당긴 후 미로를 빠져나가는 문이 있는 칸으로 이동하면 됩니다. 이때 아직 레버를 당기지 않았더라도 출구가 있는 칸을 지나갈 수 있습니다. 미로에서 한 칸을 이동하는데 1초가 걸린다고 할 때, 최대한 빠르게 미로를 빠져나가는데 걸리는 시간을 구하려 합니다.

미로를 나타낸 문자열 배열 maps가 매개변수로 주어질 때, 미로를 탈출하는데 필요한 최소 시간을 return 하는 solution 함수를 완성해주세요. 만약, 탈출할 수 없다면 -1을 return 해주세요.


제한사항
  • 5 ≤ maps의 길이 ≤ 100
    • 5 ≤ maps[i]의 길이 ≤ 100
    • maps[i]는 다음 5개의 문자들로만 이루어져 있습니다.
      • S : 시작 지점
      • E : 출구
      • L : 레버
      • O : 통로
      • X : 벽
    • 시작 지점과 출구, 레버는 항상 다른 곳에 존재하며 한 개씩만 존재합니다.
    • 출구는 레버가 당겨지지 않아도 지나갈 수 있으며, 모든 통로, 출구, 레버, 시작점은 여러 번 지나갈 수 있습니다.

나의 풀이

우선 최소 시간을 구하는 문제이므로 BFS로 풀어야겠다고 생각했다.

미로를 탈출하기 위해서는 조건이 걸려있었다.

  1. 레버를 당겨 출구를 열어 놓아야 한다.
  2. 그리고 출구를 찾아 탈출!
  3. 레버나 출구는 여러번 지나칠 수 있다.
  4. 미로는 정사각형이 아니라 직사각형이다.

이전에 풀었던 게임 맵 최단거리 문제와 비슷하게 접근하면 될 것 같다고 생각해 바로 풀어보았지만 역시 안되었다. 그 이유는 레버보다 출구를 먼저 발견했을 경우 찾아도 레버를 당기지 않았으므로 탈출할 수 없기 때문이다.

그러면 출구를 찾아도 레버를 찾으러 지나쳤다가 당기고 돌아와야한다는 건데... 너무 어려웠다.

나는 그래서 2가지 방법으로 접근했다.

  1. 레버를 찾는 맵과 출구를 찾는 맵을 따로 만들어서 각각의 맵에서 찾는 것이 아닌 것들은 일반 통로 취급을 했다.
    ex) 레버 찾는 맵에서 출구는 그냥 통로 <-> 출구 찾는 맵에서 레버는 그냥 통로
  2. 그렇게 레버를 찾았으면 레버를 찾은 곳에서 다시 출발하여 출구를 찾기로 했다.
  3. 따로 구한 거리를 더한 후 -2를 해주면 끝.
    (문제에서 시작 위치는 0이지만 문제를 풀기 위해 나는 1로 지정했기 때문이고, 맵을 2개 썼으므로 -2를 해주어야 함.)
function solution(maps) {
    const [row, column] = [maps.length, maps[0].length];
    // 레버까지 이동하기 위한 배열
    const lArr = Array.from(Array(row), (e) => Array(column).fill(0));
    // 출구까지 이동하기 위한 배열
    const eArr = Array.from(Array(row), (e) => Array(column).fill(0));
    // 상 우 하 좌로 이동하기 위한 배열
    const position = [[1,0],[0,1],[-1,0],[0,-1]];
    let need = [];
    // 레버와 출구, 시작 위치
    let l = [];
    let e = [];
    let s = [];
    // 레버와 출구의 도착 유무
    let isL = false;
    let isE = false;
    
    // 시작 위치, 레버 위치, 출구 위치 저장
    maps.forEach((v,i) => {
        if(v.includes("S")) s.push(i,v.indexOf("S"));
        if(v.includes("L")) l.push(i,v.indexOf("L"));
        if(v.includes("E")) e.push(i,v.indexOf("E"));
    })
     
    // 시작 위치에 1을 저장
    lArr[s[0]][s[1]] = 1;
    need.push(s)
  	
    while((!isL || !isE) && need.length){
        // [row, column] 현재 좌표
        const [r, c] = need.shift();
        // 레버를 당기기 위함
        if(!isL){
            for(let i = 0; i < 4; i++){
                const [newR, newC] = [r+position[i][0], c+position[i][1]];
                if(newR < 0 || newC < 0 || newR >= row || newC >= column) continue;
                if(!lArr[newR][newC] && maps[newR][newC] !== "X"){
                    // 이전 위치 값에서 1을 더해줌으로써 해당 위치까지의 거리를 알 수 있음
                    lArr[newR][newC] = lArr[r][c] + 1;
                    need.push([newR, newC]);
                }
                // 레버를 당겼으면 레버 위치를 need에 넣어주고 이제 출구를 찾음
                if(lArr[l[0]][l[1]]) {
                    isL = true;
                    need = [l];
                    eArr[l[0]][l[1]] = 1;
                }
            }
        }else{
            // 출구를 찾기 위함
            for(let i = 0; i < 4; i++){
                const [newR, newC] = [r+position[i][0], c+position[i][1]];
                if(newR < 0 || newC < 0 || newR >= row || newC >= column) continue;
                if(!eArr[newR][newC] && maps[newR][newC] !== "X"){
                    eArr[newR][newC] = eArr[r][c] + 1;
                    need.push([newR, newC]);
                }
                if(eArr[e[0]][e[1]]) {
                    isE = true;
                    need = [];
                }
            }
        }
    }
    // 각 시작 위치에 1을 넣고 시작했으므로 -2를 해줌
    return eArr[e[0]][e[1]] ? lArr[l[0]][l[1]] + eArr[e[0]][e[1]] - 2 : -1;
}

아래 사진은 이해를 돕기 위해 레버를 찾는 맵과 출구를 찾는 맵을 콘솔로 찍은 사진입니다.

profile
하루하루 공부한 내용 기록하기

0개의 댓글