每日算法总结——N皇后问题普通解法+位运算加速

问题描述:
  • N皇后问题是指在N*N的棋盘.上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。
  • 给定一个整数n,返回n皇后的摆法有多少种。
    n=1,返回1。
    n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。
    n=8,返回92。
解题思路:
  • 没有什么比较好的解法,只能通过枚举。
  • 第一行,首先放在一号位置,然后看第二行;第二行放在第一号位置,如果不与之前放过的皇后冲突(同行、同列、同斜线),则看第三行……
  • 类似于深度优先遍历
/**
 * N皇后问题,有多少摆法
 */
public static int solveNum1(int n) {
    if (n < 1) {
        return 0;
    }
    // record[i], 第i行的皇后被放在了第几列
    int[] record = new int[n];
    return process1(0, record, n);
}

/**
 * @param i      目前来到了第i行
 * @param record record[i...i-1]表示之前的行,放了皇后的位置
 * @param n      代表整体一共多少行
 * @return 返回值是,摆完所有的皇后,合理的摆法有多少种
 */
public static int process1(int i, int[] record, int n) {
    if (i == n) {
        // 来到了终止行,说明该摆法可行
        return 1;
    }
    int res = 0;
    // 尝试i行所有列是否存在可行的摆法
    for (int j = 0; j < n; j++) {
        // 判断当前i行的皇后,放在第j列会不会和之前0~i-1行的皇后,共列或者共斜线
        if (isValid(record, i, j)) {
            record[i] = j;
            res += process1(i + 1, record, n);
        }
    }
    return res;
}

public static boolean isValid(int[] record, int i, int j) {
    for (int k = 0; k < i; k++) {
        if (record[k] == j || Math.abs(record[k] - j) == i - k) {
            return false;
        }
    }
    return true;
}
位运算优化
  • 与上面的枚举思想一致,时间复杂度也一致,只不过使用位运算进行加速
  • 将整个棋盘表示成一个二进制数,比如n=8的棋盘,二进制低位就是11111111,同时这也限制了n最大只能取32(使用int类型表示,更大可以用long)
  • 每个过程有四个变量:
    • limit:一个n位掩码,n为多少,二进制低位就有多少个1
    • colLim:列限制,二进制n位上,如果之前某位对应的列放置了皇后,那么该位为1
    • leftDiaLim:左斜线的限制,1的位置不能放皇后,0的位置可以
    • rightDiaLim:右斜线的限制,1的位置不能放皇后,0的位置可以
  • 各个变量如何随行的变化而变化(假设n=8)?
    • limit:永远不变
    • colLim:如果当前行在第2列放了皇后,则colLim|01000000
    • leftDiaLim:如果当前行在第2列放了皇后,则leftDiaLim|01000000左移一位
    • rightDiaLim:如果当前行在第2列放了皇后,则rightDiaLim|01000000右移一位
/**
 * 位运算优化后的方法,因为使用的是int类型,所以无法处理32皇后以上的问题
 */
public static int solveNum2(int n) {
    if (n < 1 || n > 32) {
        return 0;
    }
    // n 为多少,limit的二进制就取多少个1,比如n=8,则limit_2=11111111
    int limit = n == 32? -1:(1 << n) - 1;
    return process2(limit, 0, 0, 0);
}

/**
 * @param limit 一个n位掩码,n为多少,二进制低位就有多少个1
 * @param colLim 列限制,二进制n位上,如果之前某位对应的列放置了皇后,那么该位为1
 * @param leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
 * @param rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
 * @return 当前条件下,可以成功放置所有皇后的情况数
 */
public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
    if (colLim == limit) {
        // 已经放完了皇后
        return 1;
    }
    int pos = 0;
    int mostRightOne = 0;
    // 将可以放置皇后的位设为1,不可以的设为0
    pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
    int res = 0;
    while (pos != 0) {
        mostRightOne = pos & (~pos + 1);
        pos = pos - mostRightOne;
        res += process2(limit, colLim | mostRightOne,
                        (leftDiaLim | mostRightOne) << 1,
                        (rightDiaLim | mostRightOne) >>> 1);
    }
    return res;
}
实战练习

LeetCode原题:51. N 皇后 - 力扣(LeetCode)

难度:Hard

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值