Skip to main content

πŸ“12952 N-Queen


ν”„λ‘œκ·Έλž˜λ¨ΈμŠ€ N-Queen https://school.programmers.co.kr/learn/courses/30/lessons/12952


1. 문제

  • κ°€λ‘œ, μ„Έλ‘œ 길이가 n인 μ •μ‚¬κ°ν˜•μœΌλ‘œ 된 체슀판인 μžˆλ‹€.
  • 체슀판 μœ„μ˜ n개의 퀸이 μ„œλ‘œλ₯Ό 곡격할 수 없도둝 λ°°μΉ˜ν•˜λ €κ³  ν•œλ‹€.
  • 예λ₯Ό λ“€μ–΄μ„œ n이 4인 경우 λ‹€μŒκ³Ό 같이 퀸을 λ°°μΉ˜ν•˜λ©΄ n개의 퀸은 μ„œλ‘œλ₯Ό ν•œλ²ˆμ— 곡격할 수 μ—†λ‹€.

image.png

image.png

체슀판의 κ°€λ‘œ μ„Έλ‘œμ˜ μ„Έλ‘œμ˜ 길이 n이 λ§€κ°œλ³€μˆ˜λ‘œ μ£Όμ–΄μ§ˆ λ•Œ, n개의 퀸이 쑰건에 λ§Œμ‘±ν•˜λ„λ‘ λ°°μΉ˜ν•  수 μžˆλŠ” λ°©λ²•μ˜ 수λ₯Ό returnν•˜λŠ” solution ν•¨μˆ˜ λ§Œλ“€κΈ°

  • ν€Έ(Queen)은 κ°€λ‘œ, μ„Έλ‘œ, λŒ€κ°μ„ μœΌλ‘œ 이동할 수 μžˆλ‹€.
  • n은 12μ΄ν•˜μ˜ μžμ—°μˆ˜μ΄λ‹€.

2. μ ‘κ·Ό 방법

μ „ν˜•μ μΈ λ°±νŠΈλž˜ν‚Ή 문제이고 DFS둜 μƒνƒœκ³΅κ°„ 트리λ₯Ό κ΅¬ν˜„ν•΄μ„œ ν‘ΈλŠ” λ¬Έμ œμ΄λ‹€. λ°±νŠΈλž˜ν‚Ήμ€ λͺ¨λ“  경우의 수λ₯Ό κ³ λ €ν•΄μ„œ ν•΄λ₯Ό μ°ΎλŠ” 방식이닀. μ΄λ¦„μ—μ„œ μœ μΆ”ν•  수 μžˆλ“―μ΄ 퇴각 검색(좔적)μ΄λΌλŠ” λœ»μ΄λ‹€. 즉, ν•΄λ₯Ό μ°ΎκΈ° μœ„ν•΄μ„œ 후보ꡰ을 λ‚˜μ—΄ν•˜κ³ , 루트 λ…Έλ“œλΆ€ν„° μ‹œμž‘ν•΄μ„œ μ œμ•½ 쑰건을 μ μ§„μ μœΌλ‘œ μ²΄ν¬ν•œλ‹€. λ§Œμ•½ 쑰건에 λ§žμ§€μ•Šλ‹€λ©΄, λ’€λ‘œ λŒμ•„μ™€μ„œ(backtrack), λ°”λ‘œ λ‹€μŒ ν›„λ³΄κ΅°μœΌλ‘œ λ„˜μ–΄κ°„λ‹€. λ°±νŠΈλž˜ν‚Ήμ—μ„œ 검색할 후보듀을 μƒνƒœ 곡간 트리(State Space Tree)둜 ν‘œν˜„ν•œλ‹€.

  • Promising : ν•΄λ‹Ή λ£¨νŠΈκ°€ 쑰건에 λ§žλŠ”μ§€ κ²€μ‚¬ν•˜λŠ” 기법
  • Pruning(κ°€μ§€μΉ˜κΈ°) : 쑰건에 λ§žμ§€ μ•ŠμœΌλ©΄ ν¬κΈ°ν•˜κ³ , λ‹€λ₯Έ 루트둜 λ°”λ‘œ λŒμ•„μ„œμ„œ νƒμƒ‰μ˜ μ‹œκ°„μ„ μ ˆμ•½ν•œλ‹€.

3. μ •λ‹΅μ½”λ“œ

class Solution {
    int count; // 클래슀 레벨 λ³€μˆ˜λ‘œ μ„ μ–Έ

    public int solution(int n) {
        count = 0;
        int[] queens = new int[n]; // index: ν–‰, value: μ—΄
        placeQueen(0, n, queens);
        return count;
    }

    // row: ν˜„μž¬ λ°°μΉ˜ν•  ν–‰, n: 체슀판 크기, queens: ν˜„μž¬κΉŒμ§€ 배치된 ν€Έ 정보
    private void placeQueen(int row, int n, int[] queens) {
        if (row == n) {
            count++; // λͺ¨λ“  행에 ν€Έ 배치 μ™„λ£Œ
            return;
        }

        for (int col = 0; col < n; col++) {
            if (isAvailable(row, col, queens)) {
                queens[row] = col; // ν€Έ 배치 
                placeQueen(row + 1, n, queens); // λ‹€μŒ ν–‰ μž¬κ·€ 호좜
            }
        }
    }

    // ν˜„μž¬ μœ„μΉ˜μ— 퀸을 놓을 수 μžˆλŠ”μ§€ 체크
    private boolean isAvailable(int row, int col, int[] queens) {
        for (int i = 0; i < row; i++) {
            // 같은 μ—΄ λ˜λŠ” λŒ€κ°μ„ μ— 퀸이 μžˆλŠ” 경우
            if (queens[i] == col || Math.abs(queens[i] - col) == row - i) {
                return false;
            }
        }
        return true;
    }
}

λ°°μ—΄λ‘œ ν’€μ—ˆλ‹€. 이 λ¬Έμ œλŠ” 이미체슀판이 4*4둜 κ³ μ •λ˜μ–΄ 있기 λ•Œλ¬Έμ— 동적 크기 관리 없이 배열을 μ“΄ 것이닀.

전체 흐름은 μ΄λ ‡κ²Œ μ§°λ‹€.

  • count μ΄ˆκΈ°ν™”: κ°€λŠ₯ν•œ 배치 수λ₯Ό μ„ΈκΈ° μœ„ν•œ λ³€μˆ˜
  • queens λ°°μ—΄ 생성: 각 행에 μ–΄λ–€ 열에 퀸이 λ†“μ˜€λŠ”μ§€ μ €μž₯
  • 첫 번째 ν–‰(row = 0)λΆ€ν„° μž¬κ·€ 호좜 μ‹œμž‘
  • λ§ˆμ§€λ§‰μ— 총 경우의 수 λ°˜ν™˜

1. placeQueen(int row, int n, int[] queens)

  • ν˜„μž¬ ν–‰(row)에 ν€Έ λ†“λŠ” ν•¨μˆ˜μ΄λ‹€
  • μ’…λ£Œ 쑰건은 row == n μ΄λ ‡κ²Œ 되면 λͺ¨λ“  행에 ν€Έ λ°°μΉ˜κ°€ μ™„λ£Œ 된 μƒνƒœλ‹ˆκΉŒ count++을 ν•œλ‹€.
  • 반볡문으둜 0 ~ n-1μ—΄κΉŒμ§€ 퀸을 놓을 수 μžˆγ„΄λŠμ§€ κ²€μ‚¬ν•œλ‹€.
  • isAvailable 체크 ν›„ κ°€λŠ₯ν•˜λ©΄
    • queens[row] = col : ν˜„μž¬ 행에 ν€Έ 배치 기둝
    • placeQueen(row + 1, ...) β†’ λ‹€μŒ ν–‰ 탐색(μž¬κ·€)

queens[row] = col

  • queens 배열은 각 ν–‰(row)에 μ–΄λ–€ μ—΄(col)에 퀸이 μžˆλŠ”μ§€ κΈ°λ‘ν•˜λŠ” μš©λ„μ΄λ‹€.
  • λ°°μ—΄μ—μ„œ queens[i] = j 라면 iν–‰ j열에 퀸을 λ°°μΉ˜ν•˜λŠ” 것이닀.
  • 예λ₯Ό λ“€μ–΄, queens[2] = 3이면 2ν–‰ 3열에 퀸이 λ†“μ˜€λ‹€λŠ” μ˜λ―Έμ΄λ‹€.
  • 이 ν•œ 쀄이 λ°”λ‘œ 퀸을 μ²΄μŠ€νŒμ— λ†“λŠ” κ²ƒμ²˜λŸΌ κΈ°λ‘ν•˜λŠ” μ½”λ“œμ΄λ‹€.
  • μ‹€μ œλ‘œ μ²΄μŠ€νŒμ„ 그리지 μ•Šκ³  λ°°μ—΄λ‘œ μœ„μΉ˜ μ •λ³΄λ§Œ μ €μž₯ν•œλ‹€.

placeQueen(row + 1, n, queens)

  • row + 1 β†’ λ‹€μŒ ν–‰(row)에 퀸을 놓기 μœ„ν•΄ μž¬κ·€ 호좜
  • 즉, ν˜„μž¬ 행에 퀸을 λ°°μΉ˜ν•œ μƒνƒœμ—μ„œ λ‹€μŒ ν–‰ 탐색을 μ‹œμž‘ν•˜λŠ” 것이닀.

2. isAvailable λ©”μ„œλ“œ

  • ν˜„μž¬ μœ„μΉ˜ (row, col)에 퀸을 놓을 수 μžˆλŠ”μ§€ κ²€μ‚¬ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.
  • 검사 λŒ€μƒ: ν˜„μž¬ 행보닀 μœ„μ— μžˆλŠ” ν€Έλ“€ (i < row)
  • 체크 쑰건:
    • queens[i] == col β†’ 같은 열에 이미 ν€Έ 있음 β†’ 놓을 수 μ—†μŒ
    • Math.abs(queens[i] - col) == row - i β†’ λŒ€κ°μ„ μ— ν€Έ 있음 β†’ 놓을 수 μ—†μŒ
  • λ‘˜ λ‹€ μ•„λ‹ˆλ©΄ μ•ˆμ „ β†’ true λ°˜ν™˜

λŒ€κ°μ„  탐색

Math.abs(queens[i] - col) == row - i

i행에 놓인 ν€Έμ˜ μ—΄ μœ„μΉ˜μ—μ„œ ν˜„μž¬ λ†“μœΌλ €λŠ” ν€Έμ˜ μ—΄ μœ„μΉ˜λ₯Ό λΊ€ κ°’κ³Ό ν˜„μž¬ 퀸을 λ†“μœΌλ €λŠ” ν–‰κ³Ό 이미 퀸이 놓인 이전 행을 λΉ„κ΅ν•˜λŠ” 것이닀.
μ²΄μŠ€μ—μ„œ 퀸은 κ°€λ‘œ,μ„Έλ‘œ, λŒ€κ°μ„ μœΌλ‘œ 곡격 κ°€λŠ₯ν•œλ° 두 μ’Œν‘œκ°€ λŒ€κ°μ„ μ— 있으면 행차이 = μ—΄μ°¨μ΄λΌλŠ” νŠΉμ„±μ„ μ΄μš©ν•œκ²ƒμ΄λ‹€.

λ‹€μ‹œλ§ν•΄

Math.abs(ν–‰1 - ν–‰2) == Math.abs(μ—΄1 - μ—΄2)

μ—¬κΈ°μ„œ:

  • ν–‰1 - ν–‰2 β†’ row - i
  • μ—΄1 - μ—΄2 β†’ col - queens[i]

κ·Έλž˜μ„œ λŒ€κ°μ„  좩돌 μ²΄ν¬λŠ” μ•„λž˜μ™€ κ°™λ‹€.

Math.abs(queens[i] - col) == row - i

예λ₯Ό λ“€μ–΄, 퀸이 (2,3)에 있고, μƒˆλ‘œμš΄ 퀸을 (4,5)에 두렀고 ν•˜λ©΄

  • ν–‰ 차이: 4 - 2 = 2
  • μ—΄ 차이: 5 - 3 = 2

λ‘˜μ΄ κ°™μœΌλ―€λ‘œ λŒ€κ°μ„  곡격 κ°€λŠ₯ β†’ 놓을 수 μ—†μŒ 원리이닀.

isAvailable λ©”μ„œλ“œ λ²”μœ„

for (int i = 0; i < row; i++)

iλŠ” 이전에 λ°°μΉ˜ν•œ ν€Έλ“€μ˜ ν–‰ 번호λ₯Ό λ‚˜νƒ€λ‚Έλ‹€. ν˜„μž¬ 퀸을 λ†“μœΌλ €λŠ” row보닀 μž‘μ€ ν–‰λ“€λ§Œ ν™•μΈν•˜λ©΄ μΆ©λΆ„ν•˜κΈ° λ•Œλ¬Έμ— λ²”μœ„κ°€ μ΄λ ‡κ²Œ λœλ‹€. μ™œλƒν•˜λ©΄ λ°±νŠΈλž˜ν‚Ήμ€ μœ„μ—μ„œ μ•„λž˜λ‘œ ν•œ ν–‰μ”© 퀸을 λ°°μΉ˜ν•˜κΈ° λ•Œλ¬Έμ—, row 이후 ν–‰μ—λŠ” 아직 퀸이 μ—†κΈ° λ•Œλ¬Έμ΄λ‹€. 즉 ν˜„μž¬ 퀸보닀 μœ— 행에 μžˆλŠ” ν€Έλ“€λ§Œ ν™•μΈν•˜λŠ” 것이닀.

예: ν˜„μž¬ 퀸을 3행에 λ†“μœΌλ €λ©΄

  • 0, 1, 2행에 이미 놓인 ν€Έλ“€κ³Όλ§Œ 좩돌 체크
  • 3ν–‰ μ΄ν›„λŠ” 아직 배치 μ•ˆ 됨 β†’ 체크 λΆˆν•„μš”

μ²˜μŒμ—λŠ” i <= rowλž‘ i < queens.length둜 ν•΄μ„œ ν‹€λ ΈλŠ”λ° row 이전 ν–‰κΉŒμ§€λ§Œ 검사해야 이미 놓은 ν€Έλ“€κ³Όμ˜ 좩돌 체크가 κ°€λŠ₯ν•˜λ‹€.