BOJ[16197] - 두 동전 by JavaScript
두 동전
문제
언어
- JavaScript
순서도
- N x M 크기의 보드에서 동전 두 개의 위치 찾기
- BFS 탐색을 진행하며, 버튼을 누른 횟수를 늘려가며 동전의 위치 변환
- 보드 밖으로 동전이 하나만 떨어졌을 때의 버튼을 누른 횟수 출력
문제 풀이 step 1
- N x M 크기의 보드가 있을 때, 두 개의 동전을 버튼을 이용해서 상하좌우로 옮겨가며, 보드에서 동전이 한 개만 떨어졌을 때의 버튼을 누른 횟수를 구하는 문제입니다.
- 우선, 주어진 보드에서 두 개의 동전의 위치를 찾습니다.
- 그리고 BFS 탐색을 실시합니다.
- 두 개의 좌표의 방문 처리를 해야하므로, 4 차원 배열을 선언합니다.
- 그리고 Queue 를 2 개를 선언해서, 단계별로 탐색을 진행합니다.
- Queue 를 2 개 사용하는 이유는 확실하게 단계를 나눠가며 버튼을 누르기 위해서입니다.
- 우선 queue 에 들어있는 모든 좌표들을 탐색하며, 다음에 탐색할 좌표들을 nextQueue 에 넣습니다.
- queue 가 텅 비게되면, nextQueue 에 있는 좌표들을 queue 로 옮겨와서 탐색을 진행합니다.
- nextQueue 에서 queue 로 옮기는 순간은 하나의 단계가 끝났음을 의미하며, 이 때 버튼을 누른 횟수를 증가시킵니다.
- 매 탐색마다 다음에 탐색할 좌표에서 동전이 몇 개 떨어지는지에 따라서 경우를 나눠서 탐색을 진행합니다.
- 하나도 안 떨어진 경우, 다음 탐색을 위해 nextQueue 에 좌표를 넣습니다.
- 한 개 떨어진 경우, 탐색을 종료하고 버튼을 누른 횟수를 출력합니다.
- 두 개 떨어진 경우, 탐색을 할 수 없습니다.
- 위 과정에 추가로 방문 처리까지 해가며 탐색을 진행합니다.
- 탐색 도중 버튼을 누른 횟수가 10 을 초과하면 탐색을 종료하고 -1 을 출력합니다.
- 최종적으로 버튼을 누른 횟수를 출력하면 정답입니다.
- 추가 설명은 주석으로 작성하겠습니다.
후기
- 꼭 다시 풀어볼 문제입니다.
- 처음 BFS 탐색을 했을 때, 두 개의 좌표를 어떻게 방문처리를 해야할 지 생각을 못했습니다. 그래서 시간이 1600 ms 가 소요되었습니다.
- 어떤 분의 블로그를 보고 4 차원 배열을 사용하는 방법도 있다는 것을 깨닫게 되었습니다.
- 영감을 얻은 곳 : https://moonsbeen.tistory.com/265
- 역시 방문 처리를 하니 소요 시간이 300 ms 까지 줄었습니다.
- 4 차원 배열을 선언해본 것도 처음이고, 이런 방법이 있다는 것을 알게 되어서 상당히 기뻤습니다.
- 조건을 제대로 파악하지 못해서, 상당히 많은 시행 착오를 겪었습니다.
- 두 동전을 떨어뜨릴 수 없거나, 버튼을 10 번 보다 많이 눌러야 한다면 -1 을 출력하는 것인데
- “앞의 두 동전을 떨어뜨릴 수 없거나” 이 부분을 못 읽어서 BFS 탐색상 로직을 잘못 짜서 불필요한 시행착오를 겪었습니다.
- BFS 함수의 마지막에 return 하는 값을 -1 로 하지 않고, depth 로 해서 이 조건을 잡아내지 못했었습니다.
- 문제를 정확히 읽어야 하고, 또 로직을 짤 때 어떤 조건이 성립이 되면 바로 return 하는 방식도 좋은 것 같습니다.
소스 코드 1
const input = require("fs")
.readFileSync("/dev/stdin")
.toString()
.trim()
.split("\n");
// 간단한 Queue 구현, 배열을 사용해도 무방
class Queue {
constructor() {
this.bucket = [];
this.rear = -1;
this.front = -1;
}
enqueue(data) {
this.bucket[++this.rear] = data;
}
dequeue() {
return this.bucket[++this.front];
}
isEmpty() {
return this.front === this.rear;
}
}
// 다음에 탐색할 좌표가 보드를 벗어나는지 검사하는 함수
const checkOut = (n, m, x, y) => 0 <= x && x < n && 0 <= y && y < m;
const BFS = (n, m, arr, start, visited) => {
const nx = [0, 0, -1, 1];
const ny = [-1, 1, 0, 0];
// 두 개의 Queue 선언
let queue = new Queue();
queue.enqueue(start);
let nextQueue = new Queue();
let depth = 1;
while (!queue.isEmpty()) {
const [[ax, ay], [bx, by]] = queue.dequeue();
// 버튼을 누른 횟수가 10 을 초과한다면 탐색 종료
if (depth > 10) break;
for (let i = 0; i < 4; i++) {
let [cx, cy] = [ax + nx[i], ay + ny[i]];
let [dx, dy] = [bx + nx[i], by + ny[i]];
// 떨어졌는지 검사
let cnt = 0;
if (!checkOut(n, m, cx, cy)) cnt += 1;
if (!checkOut(n, m, dx, dy)) cnt += 1;
// 두 개의 동전이 떨어진 경우
if (cnt === 2) continue;
// 한 개의 동전만 떨어진 경우
if (cnt === 1) return depth;
// 벽이라면 이동할 수 없음
if (arr[cx][cy] === "#") [cx, cy] = [ax, ay];
if (arr[dx][dy] === "#") [dx, dy] = [bx, by];
// 방문한 곳은 다시 탐색하지 않음
if (visited[cx][cy][dx][dy] === true) continue;
// 방문 처리 및 다음 탐색을 위해 nextQueue 에 좌표 넣기
visited[cx][cy][dx][dy] = true;
nextQueue.enqueue([
[cx, cy],
[dx, dy],
]);
}
// 버튼을 누른 횟수를 갱신하는 순간
// nextQueue 에 queue 에 있는 좌표 넣기
if (queue.isEmpty()) {
queue = nextQueue;
nextQueue = new Queue();
depth += 1;
}
}
// 두 동전을 떨어뜨릴 수 없거나, 버튼을 10 번 보다 많이 눌러야 하는 경우
return -1;
};
const solution = (input) => {
const [n, m] = input[0].split(" ").map(Number);
const arr = input.slice(1, n + 1).map((v) => v.split(""));
// 두 동전의 위치 찾기
let start = [];
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (arr[i][j] !== "o") continue;
start.push([i, j]);
}
}
// 4 차원 배열 생성
const visited = Array.from({length: n}, () =>
Array.from({length: m}, () =>
Array.from({length: n}, () => Array(m).fill(false))
)
);
// 시작 위치에 대한 방문 처리
const [[ax, ay], [bx, by]] = start;
visited[ax][ay][bx][by] = true;
const depth = BFS(n, m, arr, start, visited);
// 버튼을 누른 횟수 반환
return depth;
};
console.log(solution(input));