백준 9663: N-Queen

Song-Minhyung·2022년 5월 31일
0

Problem Solving

목록 보기
5/50
post-thumbnail

문제

https://www.acmicpc.net/problem/9663

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다.
N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.


Try 1

풀이방법

const find = (row: number, col: number, visited: number[][]) => {
    if (visited[row][col] === 1 || visited[row][col] === 2) {
        // cnt++;
        return;
    }
    visited[row][col] = 1;

    for (let i=0; i<N; i++) {
        for (let j=0; j<N; j++) {
            for (let k=0; k<pos.length; k++) {
                const x = pos[k][0] + i;
                const y = pos[k][1] + j;

                // 배열의 영역을 벗어나지 않을 경우
                // 퀸의 이동 범위를 모두 true로 체크해줌
                if ( x >= 0 && x < N 
                    && y >= 0 && y < N
                ) {
                    find( y, x, visited);
                }
            }
        }
    }
}
  1. 재귀함수 안에서 for문을 돌려 모든 경우의 수에 접근한다.
  2. queen을 놓을수 없는곳은 visited[i][j] = true로 표시한다.

Catch 1

해당 퀸이 도달할 수 없는 장소까지 true로 표기한다. 다른방법이 필요하다


Try 2

풀이방법

const N = +input();
const board = Array.from(
    {length: N}, () => 
        Array.from({length: N}, () => 0)
);
let cnt = 0;

const find = (beforeCol: number, canPutQueen: boolean[]) => {
    if (canPutQueen.every(val => val === false)) cnt++;

    for (let i=0; i<N; i++) {
        for (let j=0; j<N; j++) {
            // 만약 이전 row의 -1, 1, +1이 아닐 때
            // && 해당 col이 queen을 놓을 수 있는 위치인 경우
            if ( !(j >= beforeCol - 1 && j <= beforeCol + 1) && (canPutQueen[j] )
            ) {
                canPutQueen[j] = false;
                find( j, canPutQueen);
            }
        }
    }
}

for (let i = 0; i < N-1; i++) {
    const canPutQueen = Array(N).fill(true);
    for (let j=0; j<N; j++) {
        canPutQueen[j] = false;
        find(j, canPutQueen);
    }
}
console.log(cnt);
  1. 바깥의 for문에서 모든 경우의 수에대해 재귀함수를 돌렸음.
  2. 어떤 어떤 열에 퀸이 놓인다면 해당 열에는 퀸이 못놓이는 방식으로 접근함
  3. 0행의경우 마지막의 행도 퀸을 못놓음, 반대의 경우도 마찬가지임

Catch 2

  1. 대각선의 경우도 어디에 놓으면 안되는지 알 수 없음.

Finally 2

  1. 대각선은 어떻게 해결하면 좋을까?
    1. i, j를 더하니 왼쪽아래 -> 오른쪽 위로 향하는건 전부 같은값을 보임
    2. i, j를 빼니 왼쪽위 -> 오른쪽 아래로 향하는건 전부 같은 값을 보임
      1. 하지만 음수도 있음.
      2. 음수를 대비해서 배열을 N의 2배로 늘리면 될것같음

Try 3

풀이방법

const N = +input();
// 가로로 놓을수 있는지 확인
const canPutCol = Array(N).fill(true);
// 왼쪽아래 -> 오른쪽 위 대각선 놓을 수 있는지 확인
const rightUp = Array(N*2).fill(true);
// 왼쪽위 -> 오른쪽 아래 대각선 놓을 수 있는지 확인
const rightDown = Array(N*2).fill(true);
let cnt = 0;

const find = (row: number) => {
    if (row === N) {
        cnt ++;
        return;
    }

    for (let col=0; col<N; col++) {
        if ( canPutCol[col]
            && rightUp[row - col + N]
            && rightDown[row+col]
        ){
            canPutCol[col] = false;
            rightUp[row - col + N] = false;
            rightDown[row + col] = false;

            find( row + 1);

            canPutCol[col] = true;
            rightUp[row - col + N] = true;
            rightDown[row + col] = true;
        }
    }
}
find(0);
console.log(cnt);

풀이 방법을 계속 바꾼 끝에 드디어 성공했다.
기존에 for문을 재귀 바깥에서 한번, 안에서 한번 중복돼서 계속 돌리는게 아니라고 생각했다.
앞서 푼 N, M 문제를 생각해보니 굳이 밖에서 돌릴 필요가 없었다.
어차피 N개의 줄을 내려가면서 돌아갈거니까!!!

  • 가로줄에서 어디에 놓으면 안되는지 검사하는건 간단했다.
    그냥 배열 만들고서 해당 줄에 놓으면 못놓게 했다.
  • 대각선 체크가 문제였다. 생각해보고 시도해본건 아래와 같다
    1. 2차원 배열을 만들어서 체크한다.
      -> 시간이 너무 많이든다. 바로 넘어간다.
    2. 가로, 세로 체크하는 변수를 활용해서 체크한다.
      -> 2차원 배열을 비교하는 시간과 비슷한 시간이 든다.
    3. 대각선만 따로 계산해본다.
      -> 대각선의 i,j를 활용해 덧셈, 뺄셈을 하면 같은값이 나온다.
      -> 여기서 나온값은 8개인데, 한개의 값으로 봐도 무방하다.
      -> 이를 활용해 대각선위, 대각선아래를 계산하는 변수를 두개 만든다.
      -> 음수의 경우도 있으므로 배열의 길이에 2를 곱해준다.
  • 재귀를 들어갈 때 대각선 변수를 false로 바꾸고 재귀를 나올 때 되돌려준다.

마무리

생각보다 푸는데 시간이 오래 걸린 문제다.
하지만 문제를 풀면서 백트래킹이 정확히 어떤식의 알고리즘인지 이해가 됐다.
앞으로 비슷한 문제가 나오면 바로 풀어버릴 자신감이 생겨났다!!!

profile
기록하는 블로그

0개의 댓글