Bài toán N Hậu
Độ khó: Khó
Theo quy tắc cờ vua, quân hậu có thể tấn công các quân khác nằm cùng hàng, cùng cột hoặc cùng đường chéo.
Bài toán N Hậu nghiên cứu cách đặt n quân hậu lên bàn cờ kích thước n×n sao cho chúng không thể tấn công lẫn nhau.
Cho một số nguyên n, hãy trả về tất cả các cách đặt hậu khác nhau thỏa mãn điều kiện trên.
Mỗi giải pháp sẽ chứa một mảng các chuỗi biểu diễn vị trí của các quân hậu, trong đó 'Q' đại diện cho quân hậu và '.' đại diện cho ô trống.
Ví dụ 1:
Đầu vào: n = 4
Đầu ra: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
Giải thích: Hình minh họa cho thấy bài toán N Hậu với n=4 có hai nghiệm khác nhau.
Ví dụ 2:
Đầu vào: n = 1
Đầu ra: [["Q"]]
Điều kiện:
1 <= n <= 9
Ghi chú cốt lõi: Bài toán N Hậu
1. Tư tưởng chính (Tóm tắt bằng một câu)
"Chỉ đặt một quân hậu mỗi hàng, dùng ba mảng để kiểm tra khả năng đặt."
Ta đi từ hàng đầu tiên đến hàng cuối cùng, mỗi hàng chỉ chọn đúng một cột để đặt hậu.
Để kiểm tra nhanh liệu vị trí (r, c) có hợp lệ hay không, ta cần ba mảng boolean để lưu lại các cột, đường chéo chính và đường chéo phụ đã bị chiếm.
2. Khó khăn cốt lõi: Công thức đường chéo (Ma thuật toán học)
Làm thế nào để xác định nhanh (r, c) thuộc đường chéo nào?
-
Cột: Dễ dàng là
c. -
Đường chéo phụ (/): Giá trị
r + clà hằng số. -
Ví dụ: (0, 2), (1, 1), (2, 0) đều có tổng bằng 2.
-
Đường chéo chính (): Giá trị
r - clà hằng số. -
Ví dụ: (0, 0), (1, 1), (2, 2) đều có hiệu bằng 0.
-
Lưu ý:
r - ccó thể âm, vì vậy phải cộng thêm độ lệchn - 1để đảm bảo chỉ số không âm:r - c + n - 1.
3. Quy trình thuật toán (Ba bước)
- Khởi tạo mảng ghi nhớ (Init): Khởi tạo ba mảng boolean
col,diag1(chéo chính),diag2(chéo phụ). Độ dài củadiaglà2n - 1. - Duyệt từng hàng (DFS):
- Với hàng hiện tại
r, thử từng cộtc. - Kiểm tra: Nếu
col[c],diag1[r+c], hoặcdiag2[r-c+offset]làtrue, bỏ qua. - Đặt hậu: Đánh dấu
true, lưuqueens[r] = c. - Đệ quy: Tiếp tục với hàng
r + 1. - Hoàn tác (backtrack): Đặt lại thành
false.
- Tạo bảng cờ (Output): Chỉ khi
r == n, dùng mảngqueensđể tạo danh sách chuỗi kết quả.
Mã nguồn (có chú thích)
// Bài tập: LC 51. N-Queens
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
int[] queens = new int[n]; // queens[row] = col: quân hậu ở hàng row nằm ở cột col
boolean[] cols = new boolean[n];
boolean[] diag1 = new boolean[2 * n - 1]; // đường chéo chính: r + c
boolean[] diag2 = new boolean[2 * n - 1]; // đường chéo phụ: r - c + n - 1
backtrack(0, queens, cols, diag1, diag2, result);
return result;
}
private void backtrack(int row, int[] queens, boolean[] cols, boolean[] diag1, boolean[] diag2, List<List<String>> result) {
int size = cols.length;
if (row == size) {
// Chuyển đổi trạng thái từ mảng int sang List<String>
List<String> board = new ArrayList<>(size);
for (int col : queens) {
char[] line = new char[size];
Arrays.fill(line, '.');
line[col] = 'Q';
board.add(new String(line));
}
result.add(board);
return;
}
for (int col = 0; col < size; col++) {
int diagIndex = row - col + size - 1;
if (!cols[col] && !diag1[row + col] && !diag2[diagIndex]) {
queens[row] = col;
cols[col] = true;
diag1[row + col] = true;
diag2[diagIndex] = true;
backtrack(row + 1, queens, cols, diag1, diag2, result);
cols[col] = false;
diag1[row + col] = false;
diag2[diagIndex] = false;
}
}
}
}
Kiểm tra nhanh (Những điểm dễ nhầm)
- [ ] Kích thước mảng đường chéo?
- Là
2 * n - 1. - Lý do: Tổng lớn nhất của
r + clà2n - 2, nên mảng cần có2n - 1phần tử. - [ ] Quên công thức đường chéo chính?
- Nhớ: "Đường chéo cộng" (
/) và "đường chéo trừ" (\). diag1:r + c.diag2:r - c + (n - 1). Nếu không cộngn - 1, vị trí (0, n-1) sẽ ra chỉ số âm.- [ ] Tại sao không cần mảng hàng?
- Vì quá trình đệ quy
dfs(r, ...)→dfs(r+1, ...)xử lý từng hàng một, không bao giờ đặt hai quân hậu cùng hàng, nên không cần kiểm tra hàng.
Mô phỏng ví dụ
Giả sử n = 4, đang xét vị trí (0, 1) – đặt hậu ở hàng 0, cột 1.
- Ghi nhận:
cols[1] = truediag1[0+1 = 1] = truediag2[0-1+3 = 2] = true
- Tiếp tục: DFS(1) (hàng 1):
- Thử
(1, 0): cols[0]OK.diag1[1+0 = 1]→ False! (trùng đường chéo phụ với (0,1)).- Bỏ qua.
- Thử
(1, 1): cols[1]→ False! (trùng cột).- Bỏ qua.
- Thử
(1, 2): diag2[1-2+3 = 2]→ False! (trùng đường chéo chính với (0,1)).- Bỏ qua.
- Thử
(1, 3): - Tất cả đều OK → Đặt hậu!
(Kết quả cuối cùng: (0,1) → (1,3) → ...)