n queens 문제
이번 문제는 leet code를 돌아다니다가 좋은 답을 발견하여 이를 이해해보고자 합니다.
// js
var solveNQueens = function (n) {
// final result
let goodPlacement = [];
// chess board, initialized with '.', of size n x n
let board = Array.from(Array(n), () => new Array(n).fill("."));
let colSet = new Set(); // occupy flag for each column
let priDiagSet = new Set(); // occupy flag for each primary diagonal (i.e., Northwest <-> Southeast direction )
let secDiagSet = new Set(); // occupy flag for each secondary diagonal (i.e., Northeast <-> Southwest direction )
var isSafe = function (row, col) {
return (
!colSet.has(col) &&
!priDiagSet.has(col - row) &&
!secDiagSet.has(col + row)
);
};
var update = function (row, col, putOn) {
if (putOn == true) {
// put Queen on specified position, and set corresponding occupy flag
board[row][col] = "Q";
colSet.add(col);
priDiagSet.add(col - row);
secDiagSet.add(col + row);
} else {
// take Queen away from specified position, and clear corresponding occupy flag
board[row][col] = ".";
colSet.delete(col);
priDiagSet.delete(col - row);
secDiagSet.delete(col + row);
}
return;
};
// Notice that here we use the DFS + backtracking template, just like what we described before.
var placeQueen = function (row = 0) {
// Base case aka stop condition
// We already placed all N Queens in good position
if (row == n) {
goodPlacement.push(board.map((eachRow) => eachRow.join("")));
return;
}
// General cases:
// Try all possible columns in DFS + backtracking
for (let col = 0; col < n; col++) {
if (isSafe(row, col)) {
update(row, col, true); // make a selection
placeQueen(row + 1); // solve next row in DFS
update(row, col, false); // undo selection
}
}
return;
};
// ---------------------------------------
// Start placing Queen from row_#0
placeQueen(0);
return goodPlacement;
};
solveNQueens(4);
n-queen의 설명은 링크와 같습니다.
n-queen 문제의 핵심적인 풀이법은 아래와 같습니다.
퀸은 한 행에 하나의 퀸만 놓을 수 있습니다.
각 행의 퀸들은 북서에서 남동 방향 모든 칸들, 북동에서 남서로 이어지는 모든 칸들을 이동할 수 있습니다.
또한 북에서 남으로 동에서 서로 이어지는 모든 칸을 이동 할 수 있습니다.
이동 방향위에 다른 체스 말이 있다면 해당 말을 잡을 수 있습니다.
따라서 각 퀸은 서로의 이동 방향에 놓여있으면 안됩니다.
대략적인 풀이는 아래와 같습니다.
- 첫 행에 퀸을 놓고,
- 다음 행으로 이동합니다. (재귀 호출)
- 다음 행 n개의 열에 퀸을 놓을 수 있는지 검사합니다. (다음 행의 칸만큼 반복문 실행 + promising 검사)
- 퀸을 놓을 수 있다면 퀸을 놓고 다음 행으로 이동합니다.
- 놓을 수 없다면 반복문을 종료하고 재귀 호출한 함수를 반환해 stack을 없애줍니다.
- 다음 열로 이동해 반복합니다.
위 알고리즘에서 배울 점은 아래의 프레임입니다.
for (let col = 0; col < n; col++) {
if (isSafe(row, col)) {
update(row, col, true); // make a selection
placeQueen(row + 1); // solve next row in DFS
update(row, col, false); // undo selection
}
}
return;
n개의 행이 있다면 반복문 n번을 순회합니다.
현재 진행중인 행의 각 칸마다 하위 행 n개 만큼을 재귀호출합니다.
재귀트리를 타고 내려가다 조건에 부합하지 않는 경우 반복문을 빠져나오게 됩니다.
예를 들어 다음 행 모든 칸에 퀸을 놓을 수 없는 경우 반복문이 종료되고 재귀 함수가 반환됩니다.
반환된 재귀함수는 스택을 반환하고 해당 행의 queen을 행에서 지웁니다.
반복문이 돌아가고 다음 칸에 queen을 놓게 됩니다.
아래는 java의 풀이입니다.
class Solution {
public List<List<String>> solveNQueens(int n) {
// Chess board initialization
board = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(board[i], '.');
}
//Start placing Queen from row_#0
placeQueen( 0 );
return goodPlacement;
}
// final result
private List<List<String>> goodPlacement = new ArrayList();
private char[][] board;
// occupy flag for each column
private Set<Integer> colSet = new HashSet<Integer>();
// occupy flag for each primary diagonal (i.e., Northwest <-> Southeast direction )
private Set<Integer> priDiagSet = new HashSet<Integer>();
// occupy flag for each secondary diagonal (i.e., Northeast <-> Southwest direction )
private Set<Integer> secDiagSet = new HashSet<Integer>();
private boolean isSafe( int row, int col){
return !colSet.contains( col ) && !priDiagSet.contains( col - row ) && !secDiagSet.contains( col + row );
}
private void update( int row, int col, boolean putOn ){
if( putOn ){
// put Queen on specified position, and set corresponding occupy flag
board[row][col] = 'Q';
colSet.add( col );
priDiagSet.add( col - row );
secDiagSet.add( col + row );
}else{
// take Queen away from specified position, and clear corresponding occupy flag
board[row][col] = '.';
colSet.remove( col );
priDiagSet.remove( col - row );
secDiagSet.remove( col + row );
}
return;
}
// Notice that here we use the DFS + backtracking template, just like what we described before.
private void placeQueen( int row ){
// Base case aka stop condition
// We already placed all N Queens in good position
// Base case
if( row == board.length ){
List<String> solution = new ArrayList();
for( char[] perRow : board){
solution.add( String.copyValueOf(perRow) );
}
goodPlacement.add( solution );
return;
}
// General cases:
// Try all possible columns in DFS + backtracking
for( int col = 0 ; col < board.length ; col++){
if( isSafe(row, col) ){
update( row, col, true); // make a selection
placeQueen( row + 1 ); // solve next row in DFS
update( row, col, false); // undo selection
}
}
return;
}
}