N-Queen 문제는 체스판 위에 서로 공격하지 않는 방식으로 개의 퀸을 놓는 경우의 수를 구하는 문제이다.
퀸은 같은 행, 같은 열, 두 대각선 (좌상↘와 우상↙) 방향으로 이동할 수 있으므로, 이를 피하기 위해 배치된 퀸의 열과 대각선 상태를 추적하는 것이 포인트
이번 포스팅에서는 두 가지 접근법으로 문제 해결과 그 해결의 최적화까지 다뤄보겠음. (시간초과로 많이 틀리는 문제)
import sys
def main():
n = int(sys.stdin.readline().rstrip())
# 백트래킹 함수: row번째 행에 퀸을 배치하는 함수
def backtrack(row):
# 기저 조건: 모든 행에 퀸을 배치했다면 경우의 수 1 증가
if row == n:
nonlocal count
count += 1
return
# 현재 행(row)에서 0부터 n-1 까지 각 열(col) 탐색
for col in range(n):
# 이미 같은 열 또는 대각선에 퀸이 있다면 패스
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
# 현재 위치에 퀸 배치
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
# 다음 행으로 재귀 호출
backtrack(row + 1)
# 백트래킹: 배치했던 퀸을 제거하여 원상복구
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
count = 0
cols, diag1, diag2 = set(), set(), set()
backtrack(0)
print(count)
if __name__ == "__main__":
main()
집합(Set) 활용:
백트래킹 방식:
불리언 리스트를 사용하면 각 열과 대각선의 상태를 즉시 에 확인할 수 있음.
여기서 중요한 부분은 대각선 배열의 크기인데, 대각선 정보를 저장할 배열의 크기를 로 하는 이유와 보정 방법을 자세히 보자면 아래와 같음.
의 값은 체스판에서 최소 , 최대 까지 변할 수 있음.
예를 들어, 인 경우를 보면:
| 좌표 | |||
|---|---|---|---|
| 0 | 0 | 0 | |
| 0 | 1 | -1 | |
| 0 | 2 | -2 | |
| 0 | 3 | -3 | |
| 3 | 0 | 3 |
값의 범위: -3 ~ 3, 즉 총 가지 값가 있으므로 배열의 크기는 이어야 음수값까지도 총 갯수에 넣어 커버할 수 있게 됨.
즉, 음수 인덱스를 피하기 위해, 보정을 진행하는 것!
의 값은 체스판에서 최소 , 최대 까지 변함.
인 경우:
| 좌표 | |||
|---|---|---|---|
| 0 | 0 | 0 | |
| 0 | 3 | 3 | |
| 3 | 0 | 3 | |
| 3 | 3 | 6 |
값의 범위: 0 ~ 6, 즉 총 가지 값 → 배열 크기는 역시 .
음수 없이 자연스럽게 부터 시작하므로 별도의 보정 없이 그대로 사용
import sys
def main():
n = int(sys.stdin.readline().strip())
def backtrack(row):
nonlocal count
# 모든 행에 퀸을 배치했다면
if row == n:
count += 1
return
for col in range(n):
# 해당 열 또는 대각선에 퀸이 있으면 패스
if colUsed[col] or diag1Used[row - col + (n - 1)] or diag2Used[row + col]:
continue
# 퀸 배치
colUsed[col] = diag1Used[row - col + (n - 1)] = diag2Used[row + col] = True
backtrack(row + 1)
# 백트래킹: 원상복구
colUsed[col] = diag1Used[row - col + (n - 1)] = diag2Used[row + col] = False
count = 0
colUsed = [False] * n
# 대각선 배열 크기를 2*n-1로 설정하는 이유:
# 좌상↘: row-col 값은 -(n-1) ~ sim (n-1) → 보정 후 0 ~ 2*n-2 → 배열 크기: 2*n-1
# 우상↙: row+col 값은 0 ~ sim 2*(n-1) → 배열 크기: 2*n-1
diag1Used = [False] * (2 * n - 1)
diag2Used = [False] * (2 * n - 1)
backtrack(0)
print(count)
if __name__ == "__main__":
main()
| 구분 | 사용 식 | 값의 범위 (n=4 예시) | 보정 여부 및 결과 인덱스 범위 |
|---|---|---|---|
| 좌상↘ 대각선 | -3 ~ 3 | 보정: → 0 ~ 6 | |
| 우상↙ 대각선 | 0 ~ 6 | 보정 불필요 → 0 ~ 6 |