首页 > 试题广场 >

N皇后问题

[编程题]N皇后问题
  • 热度指数:2272 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行,不同列也不再同一条斜线上,求给一个整数n,返回n皇后的摆法。

输入描述:
输出一个整数,代表n


输出描述:
输出一个整数,代表n皇后的种数。
示例1

输入

1

输出

1
示例2

输入

8

输出

92

备注:
时间复杂度,空间复杂度
我这样写没意见吧~~
import java.util.Scanner;
public class Main {
    public static int count = 0;
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int[] arr = new int[]{0, 1, 0, 0, 2, 10, 4, 40, 92, 352, 724, 2680, 14200, 73712, 365596};
        while(scanner.hasNext()){
            int n = scanner.nextInt();
            System.out.println(arr[n]);
        }
    }
}


发表于 2019-08-19 20:16:30 回复(5)
直接用传统的深度优先搜索方式,在14皇后的时候会超时,我感觉一般掌握到这个份儿上就差不多了,优化的那个技巧真的太难想了
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        System.out.println(n < 1? 0: solve(0, new int[n], n));
    }
    
    private static int solve(int i, int[] record, int n) {
        if(i == n) return 1;      // 所有列都检查完了,找到了一种合法的摆法
        // 尝试列
        int count = 0;
        for(int j = 0; j < n; j++){
            if(isValid(i, j, record)){
                record[i] = j;
                count += solve(i + 1, record, n);
            }
        }
        return count;
    }
    
    private static boolean isValid(int i, int j, int[] record) {
        for(int k = 0; k < i; k++)
            if(record[k] == j || Math.abs(record[k] - j) == Math.abs(i - k)) return false;    // 共列或共斜线
        return true;
    }
}
这里要想过的话,得用左老师讲过的位运算优化,非常有魔性的算法:
1、对于8皇后问题,先准备一个limit,它是一个右边有8个连续的1,左边都是0的数。
2、在尝试第0行时,能够尝试的位置是  00000000,假如在某一列填了个皇后,此时变为 00001000,那么下一行不能填在这一列,且不能填在左对角线00010000,也不能填在右对角线 00000100,这三个限制合在一起得到总限制00011100。下一行只能在0的位置填皇后,我们利用limit将其转化为11100011,然后逐列进行尝试,每次消掉最右边的1。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        System.out.println(solve((1 << n) - 1, 0, 0, 0));     // 准备一个右边有n个1的数
    }
    
    private static int solve(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
        if(colLim == limit) return 1;      // 所有列都填完了,找到了一种合法的摆法
        int pos = limit & (~(colLim|leftDiaLim|rightDiaLim));      // 当前行可以摆皇后的位置
        int count = 0, mostRightOne = 0;
        while(pos != 0){
            mostRightOne = pos & (~pos + 1);       // 提取最右侧的1,表示从最右边能填的地方开始试
            pos -= mostRightOne;                   // 消掉最右侧试过的位置
            count += solve(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1);
        }
        return count;
    }
}
算法的复杂度指标上其实是没有提升的,仍然是O(2n),主要是缩短了常数时间。
编辑于 2021-11-13 15:24:55 回复(0)
#include<bits/stdc++.h>
using namespace std;
const int N = 30;
bool col[N],dg[N],udg[N];
int n ,res;

int dfs(int x){
    if(x == n) res ++;
    for(int i = 0;i < n;i++){
        if(!col[i] && !dg[x + i] && !udg[n - x + i]){
            col[i] = dg[x + i] = udg[n - x + i] = true;
            dfs(x + 1);
            col[i] = dg[x + i] = udg[n - x + i] = false;
        }
    }
    return res;
}

int main(){
    cin >> n;
    cout << dfs(0) << endl;
    return 0;
}
发表于 2024-01-29 17:33:56 回复(0)
#include <stdio.h>

typedef unsigned int uint;

uint proc(uint mask, uint colPut, uint leftLim, uint rightLim) {
    if (mask == colPut) {
        return 1;
    }
    uint ans = 0, pos, mostRight;
    pos = mask & (~(colPut | leftLim | rightLim));
    while (pos != 0) {
        mostRight = pos & (~pos + 1);
        pos -= mostRight;
        ans += proc(mask, colPut | mostRight,
                    (leftLim | mostRight) << 1,
                    (rightLim | mostRight) >> 1);
    }
    return ans;
}

int main(void) {
    int n;
    scanf("%d", &n);
    uint mask = (1 << n) - 1;
    printf("%u\n", proc(mask, 0, 0, 0));
    return 0;
}

发表于 2022-01-29 20:12:23 回复(0)
#include <iostream>
#include <vector>
using namespace std;

int process(int upperLim, int colLim, int leftDiaLim, int rightDiaLim){
    if(upperLim == colLim)
        return 1;
    int pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
    int mostRightOne = 0;
    int res = 0;
    while(pos != 0){
        mostRightOne = pos & (~pos + 1);
        res += process(upperLim, 
                       colLim | mostRightOne, 
                       (leftDiaLim | mostRightOne) << 1,
                       (rightDiaLim | mostRightOne) >> 1);
        pos = pos - mostRightOne;
    }
    return res;
}

int main(void){
    int N;
    cin >> N;
    int upperLim = -1;
    if(N < 32)
        upperLim = (1 << N) - 1;
    cout << process(upperLim, 0, 0, 0) << endl;
    return 0;
}
使用位运算来解决该问题
int process(upperLim, colLim, leftDiaLim, rightDiaLim)
获得不受限制的下标bit:: int pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim))
获得pos中位为1的数据:
int mostRightOne = pos & (~pos + 1);
pos = pos - mostRightOne;
发表于 2021-05-26 15:29:24 回复(0)
思路没有变,递归搜索,时间复杂度还是O(2^n);只是改成用位运算来模拟,减少了空间复杂度至O(1)。

而且位运算比之前快了些,可以通过时间限制,不会又提示“运行超时”了。

import java.util.*;

public class Main {
    
    public static int process(int checkerboard, int current, int leftDiagonal, int rightDiagonal) {
        if (checkerboard == current) {
            // 每一列都放了棋子,放完了
            return 1;
        }
        int res = 0;
        // 找可以放棋子的位置,位上为1可以放置
        // 00 1 0 0 0    leftDiagonal
        // 00 0 0 0 1    rightDiagonal
        // 00 0 0 1 0    current
        // 00 1 0 1 1
        // 11 0 1 0 0
        // 00 1 1 1 1    checkerboard
        // 00 0 1 0 0    pos
        // 00 0 1 0 0    rightmost
        int pos = checkerboard & (~ (leftDiagonal | rightDiagonal | current));
        while (pos != 0) {
            int rightmost = (~pos + 1) & pos;
            pos ^= rightmost;
            // 放棋子,影响后面放置的位置
            res += process(checkerboard, current | rightmost, 
                           (leftDiagonal | rightmost) << 1, (rightDiagonal | rightmost) >>> 1);
        }
        return res;
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        if (n < 1 || n > 32) {
            System.out.println(0);
            return;
        }
        // 位上为1,可以放棋子
        int checkerboard = n == 32 ? -1 : (1 << n) - 1;
        int result = process(checkerboard, 0, 0, 0);
        System.out.println(result);
    }
}
发表于 2020-05-10 15:55:53 回复(0)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
    int cnt = 0;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(new Main().totalNQueens(n));
    }

    public int totalNQueens(int n) {
        if(n < 1){
            return 0;
        }
        List<Integer> list = new ArrayList<>();

        help(0,n,list);
        return cnt;
    }
    private void help(int row, int n, List<Integer> list){
        //递归的第一步,列出终止条件,防止死循环
        if(row == n){
            cnt++;
            return;

        }
        //每一列都尝试一下
        for (int col = 0; col < n; col++) {
            //当前列是否合法
            if (!list.contains(col)) {
                //左斜与右协是否哈法
                if(!isDiagonalAttack(list,col)){
                    list.add(col);
                    help(row+1,n,list);
                    //回溯
                    list.remove(list.size()-1);
                }
            }

        }

    }

    private boolean isDiagonalAttack(List<Integer> currentQueen, int i) {
        int currentRow = currentQueen.size();
        int currentCol = i;
        //判断每一行的皇后的情况
        for (int row = 0; row < currentQueen.size(); row++) {
            //左上角的对角线和右上角的对角线,差要么相等,要么互为相反数,直接写成了绝对值
            if (Math.abs(currentRow - row) == Math.abs(currentCol - currentQueen.get(row))) {
                return true;
            }
        }
        return false;
    }
}

发表于 2019-08-23 09:27:00 回复(1)