【算法学习笔记】N皇后问题

介绍

N皇后问题是指在N*N的棋盘上摆放N个皇后,要求任何两个皇后不同行、不同列、不在同一条斜线上。给定一个整数n,返回n皇后的摆法有多少种。例如1皇后有1种摆法,2或3皇后无解。

解法

按行遍历递归

由于每一行必定有一个皇后,第i个皇后一定摆放在第i行。我们可以按行遍历,寻找每一行皇后可能的列摆放位置j,由于每一行都有n个可能位置,所以时间复杂度是 O ( n n ) O(n^n) O(nn)。代码如下:

// 按行遍历递归
public int nQueen(int n) {
    if (n < 1) {
        return 0;
    }
    // record[i]代表第i个皇后放在了第i行的第几列
    int[] record = new int[n];
    return process(0, record, n);
}

// row 代表当前进行的是第i行的判断(0到row-1行一定已经按照规则摆好)
// n 代表总行数
// 返回值是合理的摆法总数
public int process(int row, int[] record, int n) {
    if (row == n) {
        // 基线条件:如果当前所有行已经判断完,则已经得出一种摆法
        return 1;
    }
    int res = 0;
    for (int col = 0; col < n; ++col) {
        // 判断当前位置是否会和之前的位置冲突
        if (isValid(record, row, col)) {
            // 有效的摆法,进入下一行
            record[row] = col;
            res += process(row + 1, record, n);
        }
    }
    return res;
}

// 判断row行的皇后位置与0到row-1行的皇后位置是否有冲突
public boolean isValid(int[] record, int row, int col) {
    // 判断方法:列是否相等+是否在同一斜线上(斜率45°)
    for (int i = 0; i < row; ++i) {
        if (record[i] == col || (row - i) == Math.abs(col - record[i])) {
            return false;
        }
    }
    return true;
}

位运算

位运算思路与按行遍历递归一样,但是可以在常数级别上优化运行速度(虽然时间复杂度仍然是 O ( n n ) O(n^n) O(nn),但运行速度更快),代码如下:

// 位运算优化,注意,n不能大于所用数据类型的二进制位数(如int型是32,则n不能大于32)
public int nQueenBit(int n) {
    if (n < 1 || n > 32) {
        return 0;
    }
    // 构建用于判定位置的二进制数,n皇后问题就用n个二进制1表示
    // 这也是为什么n有限制
    int limit = n == 32 ? -1 : ((1 << n) - 1);
    return process(limit, 0, 0, 0);
}

// colLim 列的限制,1的位置不能放皇后,0的位置可以放皇后
// leftDiaLim 左斜线的限制,同上
// rightDiaLim 右斜线的限制,同上
public int process(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
    if (colLim == limit) {
        // 基线条件:与递归方式相同,如果全部摆完,即得到一种摆法
        return 1;
    }
    int pos = 0;
    int mostRightOne = 0;
    // (colLim | leftDiaLim | rightDiaLim)得到当前行不能摆放皇后的位置,取反则1是可以拜访皇后的位置,与limit & 是限制比较在固定位上进行,防止更高位影响判断
    // 例如8皇后问题,limit的9位以上都是0,与limit & 即可排除高位影响
    pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
    int res = 0;
    while (pos != 0) {
        // 取pos的最右一位1,即最右的可以放皇后的位置
        mostRightOne = pos & (~pos + 1);
        // 去除最右1
        pos = pos - mostRightOne;
        // colLim | mostRightOne,进入下一行,要避开的列多了当前位置,进行或运算即可
        // (leftDiaLim | mostRightOne) << 1 进入下一行,前面所有行的左斜线影响都会向左偏移一位,直接或运算再左移即可
        // 右斜线同左斜线,注意是无符号右移保证整体右移
        res += process(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1);
    }
}

四皇后问题的运行过程示例:
四皇后问题示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值