N-Queens dan Propagasi Kendala
Tempatkan N ratu pada papan N×N menggunakan himpunan kolom dan diagonal untuk pemeriksaan kendala O(1), lalu bahas perbedaan antara menghitung dan mengenumerasikan solusi
N-Queens dan Propagasi Kendala adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Masalah N-Ratu
Masalah N-Ratu (LeetCode 51/52) meminta Anda menempatkan N ratu pada papan catur berukuran N×N sehingga tidak ada dua ratu yang saling menyerang. Ratu dapat menyerang sepanjang baris, kolom, dan kedua diagonal. Untuk N=4, terdapat tepat 2 solusi. Untuk N=8 (versi klasik), terdapat 92 solusi. Ini adalah masalah penelusuran mundur klasik dengan pemeriksaan kendala yang memangkas ruang pencarian secara drastis.
# N-Queens constraints:
# 1. Exactly one queen per row
# 2. No two queens in the same column
# 3. No two queens on the same diagonal (top-left to bottom-right)
# 4. No two queens on the same anti-diagonal (top-right to bottom-left)
# For N=4, the 2 solutions:
sol1 = ['.Q..', '...Q', 'Q...', '..Q.']
sol2 = ['..Q.', 'Q...', '...Q', '.Q..']
print('N=4 solutions:')
for row in sol1: print(row)
print()
for row in sol2: print(row)Menempatkan Satu Ratu di Setiap Baris
Karena tidak ada dua ratu yang dapat menempati baris yang sama, kita menempatkan tepat satu ratu di setiap baris. Penelusuran mundur dilakukan baris demi baris, dengan memilih satu kolom untuk setiap baris. Cara ini mengurangi ruang pencarian dari N² pilihan per ratu menjadi hanya N kolom per baris, sehingga menghasilkan N^N cabang awal — tetapi kendala menguranginya secara drastis. Kedalaman rekursinya adalah N (satu tingkat untuk setiap baris), dan faktor percabangannya paling banyak N.
def solve_n_queens(n):
results = []
queens = [] # queens[row] = column of queen in that row
def backtrack(row):
if row == n:
# Build the board representation
board = []
for r in range(n):
board.append('.' * queens[r] + 'Q' + '.' * (n - queens[r] - 1))
results.append(board)
return
for col in range(n):
if is_valid(row, col):
queens.append(col) # CHOOSE
backtrack(row + 1) # EXPLORE
queens.pop() # UNCHOOSE
def is_valid(row, col):
for r, c in enumerate(queens):
if c == col: return False # same column
if abs(row - r) == abs(col - c): return False # diagonal
return True
backtrack(0)
return results
print(len(solve_n_queens(4)), 'solutions for N=4') # 2
print(len(solve_n_queens(8)), 'solutions for N=8') # 92Pemeriksaan Kendala O(1) dengan Himpunan
Memeriksa validitas dengan memindai semua ratu yang telah ditempatkan membutuhkan O(N) untuk setiap kandidat, sehingga algoritme secara keseluruhan memiliki kompleksitas terburuk O(N² × N!). Kita dapat mengurangi setiap pemeriksaan validitas menjadi O(1) dengan mempertahankan tiga himpunan: cols (kolom yang terisi), diag (nilai baris-kolom untuk diagonal kiri atas), dan anti_diag (nilai baris+kolom untuk diagonal kanan atas). Ratu pada diagonal yang sama memiliki nilai baris-kolom yang sama; pada antidiagonal yang sama, mereka memiliki nilai baris+kolom yang sama.
def solve_n_queens_fast(n):
results = []
cols = set() # occupied columns
diag = set() # row - col (positive diagonal)
anti = set() # row + col (negative diagonal)
queens = []
def backtrack(row):
if row == n:
board = ['.' * c + 'Q' + '.' * (n-c-1) for c in queens]
results.append(board)
return
for col in range(n):
if col in cols or (row-col) in diag or (row+col) in anti:
continue # PRUNE: constraint violated
# CHOOSE
cols.add(col); diag.add(row-col); anti.add(row+col); queens.append(col)
backtrack(row + 1) # EXPLORE
# UNCHOOSE
cols.remove(col); diag.remove(row-col); anti.remove(row+col); queens.pop()
backtrack(0)
return results
print(len(solve_n_queens_fast(8))) # 92Penjelasan Invarian Diagonal
Inti pemahaman tentang diagonal: semua sel pada diagonal yang sama dari kiri atas ke kanan bawah memiliki nilai row - col yang sama. Misalnya, (0,0), (1,1), dan (2,2) semuanya memiliki row-col=0. Semua sel pada antidiagonal yang sama memiliki nilai row + col yang sama: (0,2), (1,1), dan (2,0) semuanya memiliki row+col=2. Invarian waktu konstan inilah yang memungkinkan kita memeriksa konflik diagonal dengan pencarian pada himpunan O(1), bukan dengan pemindaian linear O(N).
# Visualise the diagonal invariants for a 4x4 board
n = 4
print('row-col values (same diagonal):')
for r in range(n):
print([r-c for c in range(n)])
print('row+col values (same anti-diagonal):')
for r in range(n):
print([r+c for c in range(n)])
# Verify: (0,0) and (2,2) share diag value 0
print('(0,0) diag:', 0-0, '| (2,2) diag:', 2-2) # both 0
# Verify: (0,2) and (2,0) share anti-diag value 2
print('(0,2) anti:', 0+2, '| (2,0) anti:', 2+0) # both 2Menghitung Solusi: N-Ratu II
N-Ratu II (LeetCode 52) hanya meminta jumlah solusi, bukan papan solusinya. Ini memungkinkan sedikit optimasi: lewati langkah pembuatan papan dan cukup tambahkan satu ke penghitung. Menggunakan masker bit alih-alih himpunan dapat lebih mempercepat penghitungan hingga mendekati O(1) per operasi. Jumlah solusi bertambah secara tidak monoton: 1(N=1), 0(N=2), 0(N=3), 2(N=4), 10(N=5), 4(N=6), 40(N=7), 92(N=8).
def total_n_queens(n):
count = [0]
cols = set(); diag = set(); anti = set()
def backtrack(row):
if row == n:
count[0] += 1
return
for col in range(n):
if col in cols or (row-col) in diag or (row+col) in anti:
continue
cols.add(col); diag.add(row-col); anti.add(row+col)
backtrack(row + 1)
cols.remove(col); diag.remove(row-col); anti.remove(row+col)
backtrack(0)
return count[0]
for n in range(1, 11):
print(f'N={n}: {total_n_queens(n)} solutions')N-Ratu dengan Masker Bit untuk Kecepatan
Untuk N yang sangat besar, implementasi masker bit berjalan jauh lebih cepat. Gunakan tiga bilangan bulat sebagai masker bit: cols, left_diag (bergeser ke kiri pada setiap baris), dan right_diag (bergeser ke kanan pada setiap baris). Kolom yang tersedia adalah ((1<<n)-1) & ~(cols|left_diag|right_diag). Ambil setiap kolom yang tersedia dengan bit = available & -available (bit terendah yang aktif), lalu lakukan rekursi. Cara ini mencapai O(1) untuk setiap pemeriksaan kendala menggunakan operasi bit.
def total_n_queens_bitmask(n):
full = (1 << n) - 1 # all n columns set
count = [0]
def bt(cols, left_diag, right_diag):
if cols == full:
count[0] += 1
return
available = full & ~(cols | left_diag | right_diag)
while available:
bit = available & -available # lowest set bit
available &= available - 1 # remove lowest bit
bt(cols | bit,
(left_diag | bit) << 1,
(right_diag | bit) >> 1)
bt(0, 0, 0)
return count[0]
for n in range(1, 13):
print(f'N={n}: {total_n_queens_bitmask(n)}')Konsep Propagasi Kendala
Propagasi kendala melampaui pemangkasan sederhana: setelah menempatkan satu ratu, segera simpulkan dan hilangkan semua posisi yang tidak valid pada baris-baris berikutnya. Pendekatan ini lebih agresif daripada memeriksa validitas pada setiap kandidat — Anda secara proaktif mempersempit ruang pencarian sebelum melakukan percabangan. Contoh paling terkenal adalah Konsistensi Busur dalam pemecah SAT dan pemecah Sudoku, ketika penempatan satu angka menghilangkan pilihan pada baris, kolom, dan kotak 3×3 yang sama.
# Constraint propagation in Sudoku:
# After placing 5 in cell (0,0):
# - Row 0: no other cell can have 5
# - Column 0: no other cell can have 5
# - Box (0,0)-(2,2): no other cell can have 5
# This is propagated BEFORE branching further
# Simple demo: remaining valid columns after placing queens
def remaining_columns(n, queens):
cols = set(q for q in queens)
diags = set(r - q for r, q in enumerate(queens))
anti_diags = set(r + q for r, q in enumerate(queens))
row = len(queens)
return [c for c in range(n)
if c not in cols
and (row-c) not in diags
and (row+c) not in anti_diags]
print(remaining_columns(8, [0])) # valid cols for row 1 after placing col 0 in row 0Pemecah Sudoku
Sudoku merupakan contoh baku masalah propagasi kendala. Pada setiap sel kosong, pilihan angka yang valid adalah angka yang belum ada pada baris, kolom, atau kotak 3×3 yang sama. Pemecah dengan penelusuran mundur bekerja dengan cara: menemukan sel kosong pertama, mencoba setiap angka yang valid, lalu melakukan rekursi. Jika mencapai kontradiksi (sel kosong tanpa angka yang valid), lakukan penelusuran mundur. Pemecah Sudoku yang baik juga menerapkan propagasi kendala (catatan kandidat) sebelum melakukan penelusuran mundur.
def solve_sudoku(board):
def is_valid(r, c, num):
for i in range(9):
if board[r][i] == num: return False # row
if board[i][c] == num: return False # col
br, bc = (r//3)*3, (c//3)*3
for i in range(3):
for j in range(3):
if board[br+i][bc+j] == num: return False # box
return True
def backtrack():
for r in range(9):
for c in range(9):
if board[r][c] == '.':
for d in '123456789':
if is_valid(r, c, d):
board[r][c] = d
if backtrack(): return True
board[r][c] = '.'
return False # no valid digit found
return True # no empty cells: solved
backtrack()
return board
# Mini test with a solvable board (simplified)
print('Sudoku solver implemented')Heuristik Variabel Paling Terkendala
Optimisasi penting untuk masalah pemenuhan kendala: selalu pilih variabel yang paling terkendala (sel dengan pilihan valid paling sedikit) berikutnya. Dalam Sudoku, jika satu sel hanya memiliki 1 angka valid, pengisian sel tersebut segera bersifat wajib — tidak diperlukan penelusuran mundur. Memilih sel-sel seperti ini terlebih dahulu secara drastis mengurangi kedalaman pohon pencarian. Ini adalah heuristik Nilai Tersisa Minimum (MRV) dari pemrograman kendala dalam kecerdasan buatan.
def solve_sudoku_mrv(board):
'''Find cell with fewest valid choices (MRV heuristic).'''
def valid_choices(r, c):
nums = set('123456789')
for i in range(9):
nums.discard(board[r][i])
nums.discard(board[i][c])
br, bc = (r//3)*3, (c//3)*3
for i in range(3):
for j in range(3):
nums.discard(board[br+i][bc+j])
return nums
def find_mrv():
best = (10, -1, -1, set()) # (choices_count, r, c, choices)
for r in range(9):
for c in range(9):
if board[r][c] == '.':
choices = valid_choices(r, c)
if len(choices) < best[0]:
best = (len(choices), r, c, choices)
return best[1], best[2], best[3]
def backtrack():
r, c, choices = find_mrv()
if r == -1: return True # no empty cells
for d in choices:
board[r][c] = d
if backtrack(): return True
board[r][c] = '.'
return False
backtrack()
return boardTabel Jumlah Solusi N-Ratu
Jumlah solusi N-Ratu mengikuti urutan yang sudah dikenal luas: N=1: 1, N=2: 0, N=3: 0, N=4: 2, N=5: 10, N=6: 4, N=7: 40, N=8: 92, N=9: 352, N=10: 724. Belum diketahui rumus bentuk tertutup; jumlahnya harus dihitung. Untuk N=27, terdapat sekitar 2,34 × 10^17 solusi. Pertanyaan wawancara biasanya meminta N ≤ 9. Memahami pertumbuhan eksponensial ini menjelaskan mengapa optimisasi bitmask penting untuk N yang lebih besar.
def count_queens(n):
'''O(1) per constraint check using sets.'''
count = [0]
cols = set(); diag = set(); anti = set()
def bt(row):
if row == n: count[0] += 1; return
for col in range(n):
if col in cols or (row-col) in diag or (row+col) in anti: continue
cols.add(col); diag.add(row-col); anti.add(row+col)
bt(row+1)
cols.discard(col); diag.discard(row-col); anti.discard(row+col)
bt(0)
return count[0]
sequence = [count_queens(n) for n in range(1, 12)]
print('N-Queens counts:', sequence)
# [1, 0, 0, 2, 10, 4, 40, 92, 352, 724, 2680]Konstruksi Papan N-Ratu
Ketika pewawancara meminta Anda mengembalikan papan sebenarnya (LeetCode 51), buat setiap papan dari daftar queens, dengan queens[r] sebagai kolom ratu pada baris r. Konstruksi string: '.' * col + 'Q' + '.' * (n - col - 1) untuk setiap baris. Konstruksi O(n²) ini hanya dipanggil pada daun pohon rekursi (ketika semua N ratu telah ditempatkan), sehingga tidak memengaruhi kompleksitas keseluruhan.
def n_queens_boards(n):
results = []
queens = []
cols = set(); diag = set(); anti = set()
def build_board():
return ['.' * c + 'Q' + '.' * (n-c-1) for c in queens]
def bt(row):
if row == n:
results.append(build_board())
return
for col in range(n):
if col in cols or (row-col) in diag or (row+col) in anti: continue
cols.add(col); diag.add(row-col); anti.add(row+col); queens.append(col)
bt(row+1)
cols.remove(col); diag.remove(row-col); anti.remove(row+col); queens.pop()
bt(0)
return results
for board in n_queens_boards(4):
for row in board: print(row)
print()Pemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: N-Ratu menempatkan satu ratu pada setiap baris dan menggunakan himpunan untuk kolom, diagonal (baris-kolom), serta antidiagonal (baris+kolom) guna melakukan pemeriksaan kendala dalam O(1), bitmask semakin mempercepat pemeriksaan kendala dan memungkinkan penelusuran semua penempatan dengan biaya mendekati O(1) per operasi, serta propagasi kendala (heuristik MRV) mengurangi pencarian dengan selalu memilih variabel yang paling terkendala berikutnya. Selanjutnya, kita akan membandingkan pendekatan Serakah dan DP serta mempelajari kapan masing-masing harus diterapkan.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “N-Queens dan Propagasi Kendala” gratis?
Ya — teks lengkap “N-Queens dan Propagasi Kendala” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “N-Queens dan Propagasi Kendala”?
Tempatkan N ratu pada papan N×N menggunakan himpunan kolom dan diagonal untuk pemeriksaan kendala O(1), lalu bahas perbedaan antara menghitung dan mengenumerasikan solusi Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “N-Queens dan Propagasi Kendala” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
- Himpunan Bagian dan Himpunan Kuasa
- Permutasi dan Kombinasi
- N-Queens dan Propagasi Kendala