0Pricing
DSA Interview Prep · Pelajaran

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')  # 92

Pemeriksaan 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)))  # 92

Penjelasan 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 2

Menghitung 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 0

Pemecah 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 board

Tabel 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

  1. Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
  2. Himpunan Bagian dan Himpunan Kuasa
  3. Permutasi dan Kombinasi
  4. N-Queens dan Propagasi Kendala
← Kembali ke DSA Interview Prep