[Problem Solving] 리코쳇 로봇

Sean·2023년 9월 6일
0

Problem Solving

목록 보기
64/130

문제

리코쳇 로봇이라는 보드게임이 있습니다.

이 보드게임은 격자모양 게임판 위에서 말을 움직이는 게임으로, 시작 위치에서 목표 위치까지 최소 몇 번만에 도달할 수 있는지 말하는 게임입니다.

이 게임에서 말의 움직임은 상, 하, 좌, 우 4방향 중 하나를 선택해서 게임판 위의 장애물이나 맨 끝에 부딪힐 때까지 미끄러져 이동하는 것을 한 번의 이동으로 칩니다.

다음은 보드게임판을 나타낸 예시입니다.

...D..R
.D.G...
....D.D
D....D.
..D....

여기서 "."은 빈 공간을, "R"은 로봇의 처음 위치를, "D"는 장애물의 위치를, "G"는 목표지점을 나타냅니다.
위 예시에서는 "R" 위치에서 아래, 왼쪽, 위, 왼쪽, 아래, 오른쪽, 위 순서로 움직이면 7번 만에 "G" 위치에 멈춰 설 수 있으며, 이것이 최소 움직임 중 하나입니다.

게임판의 상태를 나타내는 문자열 배열 board가 주어졌을 때, 말이 목표위치에 도달하는데 최소 몇 번 이동해야 하는지 return 하는 solution함수를 완성하세요. 만약 목표위치에 도달할 수 없다면 -1을 return 해주세요.

제한 사항

  • 3 ≤ board의 길이 ≤ 100
    • 3 ≤ board의 원소의 길이 ≤ 100
    • board의 원소의 길이는 모두 동일합니다.
    • 문자열은 ".", "D", "R", "G"로만 구성되어 있으며 각각 빈 공간, 장애물, 로봇의 처음 위치, 목표 지점을 나타냅니다.
    • "R"과 "G"는 한 번씩 등장합니다.

풀이

아이디어

  • 이 문제를 대충 읽으면 안 된다. 그냥 단순 BFS인 줄 알고 그냥 무작정 BFS로 구현했다가, 문제를 다시 읽어보니 BFS이긴 한데 조건이 좀 붙은 BFS였다.
  • 우선, 시작점과 끝점의 좌표를 구한다.
    • 시작점을 queue에다가 넣어준다. 이때, queue에는 좌표에다가 움직인 횟수까지 같이 배열에 넣어서 queue는 총 원소가 3개인 배열을 원소로서 취급한다.
  • 상하좌우를 움직일 수 있는 move 배열을 const로 선언한다.
  • BFS의 기본인 while(queue.length)로 시작해서 탐색에 들어간다.
    • Array.prototype.shift()를 통해서 맨 앞의 점을 반환받는다.
    • 해당 점이 board에서 "G"이면 곧장 해당 원소의 카운트를 answer로 설정 후 반환한다.
    • "G"가 아니면 다음 로직을 따른다.
      • 상/하/좌/우 4방향으로 탐색해야 하기 때문에 for문을 4번 돌린다.
      • 각 방향으로 움직였을 때 board판의 범위 내에 있거나 "D"를 만날때까지 움직인다.
      • "D"를 만났다면 "D"를 만나기 한 칸 전으로 옮겨준 후, 해당 점을 방문하지 않았었다면 visited값을 true로 바꿔주고 queue에도 현재 카운트+1을 추가하여 좌표를 저장한다.
  • 로직을 반복했을 때 answer이 여전히 0이라면 -1을 반환하고, 아니라면 answer를 반환한면 된다.

코드

const move = [[-1, 0], [1, 0], [0, -1], [0, 1]];

function solution(board) {
    let answer = 0;
    let boardR = board.length, boardC = board[0].length;
    //시작점과 도작점 구하기
    let R = [0, 0], G = [0, 0];
    board.forEach((row, ridx) => {
        if(row.includes("R")) {
            R = [ridx, row.indexOf("R")];
        }
        if(row.includes("G")) {
            G = [ridx, row.indexOf("G")];
        }
    });
    
    let queue = [[...R, 0]];
    let visited = Array(boardR).fill(false).map(elem => Array(boardC).fill(false));
    visited[R[0]][R[1]] = true;
    let count = 0;
    
    while(queue.length) {
        //상하좌우 네방향에 대해서 수행
        let [curR, curC, cnt] = queue.shift();
        if(curR == G[0] && curC == G[1]) {
            answer = cnt;
            break;
        }
        for(let i=0; i<4; i++) {
            const dir = move[i];
            let newR = curR + move[i][0],
                newC = curC + move[i][1];
            
            while(newR >=0 && newR < boardR && newC >=0 && newC < boardC && board[newR][newC] !== 'D') {
                newR += move[i][0];
                newC += move[i][1];
            }
            
            newR -= move[i][0];
            newC -= move[i][1];
            
            if(!visited[newR][newC]) {
                queue.push([newR, newC, cnt+1]);
                visited[newR][newC] = true;
            }
        }
    }
    
    return answer == 0 ? -1 : answer;
}

돌아보기

처음에 상/하/좌/우에 대한 로직을 따로따로 써주려다보니까 코드가 많이 더러워졌고, 에러 시 백트래킹 하기도 어려워졌다.
그런데 상/하/좌/우에 대한 로직을 따로 쓸 필요 없이 위 코드처럼 move배열에 움직이는 방향을 정해놓고, for문을 4번 돌린 후, while문 조건에 맞게 미끄러지면 되는 것이었다.
'미끄러진다'는 것에 살짝 멘붕이 와서 String.prototype.indexOf()를 써서 'D'를 찾고 난리도 아니었다...

profile
여러 프로젝트보다 하나라도 제대로, 깔끔하게.

0개의 댓글