
难度:困难
题目:
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。

提示:
皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。
当然,她横、竖、斜都可走一或 N-1 步,可进可退。(引用自 百度百科 - 皇后 )
--------------------------------------------
N皇后问题,经典的题目,记得大学老师很喜欢用来做教学题材,回溯法的入门经典教学用例,8皇后。
只不过这里不是8皇后,是N个皇后,其实做法都大同小异,只不过一个是写死8个皇后,一个是支持输入而已。
N皇后问题应该有耳朵的都听过了吧。
就是如果当前皇后所在位置,如果上下左右外加 斜上下左右的,已经有存在皇后的话,那就是冲突,就不能放,只能找其他位置。

5皇后例子
如果能找到符合需求的n个皇后都完美放在了棋盘中的话,那就是一个完美的答案,现在需要把所有的答案打印出来,皇后的位置是“Q”,其他空位置为“.”表示。
这是回溯法的专用教学案例,当然这里也是使用回溯法。
回溯法基本思想就是:
回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
其实回溯法在之前的《将数组拆分成斐波那契序列》中也有用到,但是那个还不够经典。
归于当前需求,并结合回溯法思想,也就是找到每行的每一个皇后在哪一列来确定他的坐标[i, j]。
这里我按列来说。
从第一列的第一行开始,判断之前的列有没冲突,如果有就向下走一格,如果没有就放下。
继续判断第二列的皇后。
。
。
当到了第n列时,如果这列的皇后放在第x行刚好和之前的没有冲突,那就是一个答案,然后再向下找(当然已经没有了,因为每一次到最后一列只有一个答案)。
那最后一列的已经找到了最后一行了呢,那就来了,回溯。
退回到前一列(第n-1列),然后前一列的皇后继续往下面的行移动,如果找到了再继续后一列(第n列)的判断。如果没有就再回溯,回到了n-2列,然后同理的操作。
当退回到第1列时,全都试探完了,那就是完了。
这里还可以优化了一下,把二维数组换成了单维数组,i-n和a[i]分别代表行和列。
递归实现:
public List> solveNQueens(int n) {    List> solutions = new ArrayList>();    int[] queens = new int[n];    Arrays.fill(queens, -1);    Set columns = new HashSet();    Set diagonals1 = new HashSet();    Set diagonals2 = new HashSet();    backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);    return solutions;}public void backtrack(List> solutions, int[] queens, int n, int row, Set columns, Set diagonals1, Set diagonals2) {    if (row == n) {        List board = generateBoard(queens, n);        solutions.add(board);    } else {        for (int i = 0; i < n; i++) {            if (columns.contains(i)) {                continue;            }            int diagonal1 = row - i;            if (diagonals1.contains(diagonal1)) {                continue;            }            int diagonal2 = row + i;            if (diagonals2.contains(diagonal2)) {                continue;            }            queens[row] = i;            columns.add(i);            diagonals1.add(diagonal1);            diagonals2.add(diagonal2);            backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);            queens[row] = -1;            columns.remove(i);            diagonals1.remove(diagonal1);            diagonals2.remove(diagonal2);        }    }}public ListgenerateBoard(int[] queens, int n) {    List board = new ArrayList();    for (int i = 0; i < n; i++) {        char[] row = new char[n];        Arrays.fill(row, '.');        row[queens[i]] = 'Q';        board.add(new String(row));    }    return board;}时间复杂度:O(n的n次方)
空间复杂度:O(n+x)
非递归实现:
class Solution {    public List> solveNQueens(int n) {        List> lists = new ArrayList<>();        int i = 1;        // 用数组a存储棋子坐标,可以理解为i代表列,a[i]代表行        int[] a = new int[n+1];        while (i > 0) {            // i为当前列,寻找前面各列与当前第i列的排斥情况,拿到的a[i]就是当前行i的合适a[i]列            for (a[i]++; a[i]<=n; a[i]++) if (check2(a, i)) break;            // 如果a[i]列小于n,则可以继续向后找            if (a[i] <= n) {                // 如果当前行i就是第n行,则数量加1                if (i == n) {                    Listlist = new ArrayList<>();                    for (int i2 : a) {                        StringBuilder sb = new StringBuilder();                        for (int j = 0; j < n; j++) {                            if (j + 1 == i2) sb.append("Q");                            else sb.append(".");                        }                        list.add(sb.toString());                    }                    list.remove(0);                    lists.add(list);                    // 否则就是向后一列找,并且后面一列无论是有没找过都要重置为0;                } else {                    i++;                    a[i] = 0;                }                // 否则就是回溯,回到前一列(然后继续向下面行找)            } else {                i--;            }        }        return lists;    }    private static boolean check2(int[] a, int n) {        for (int i=1; i            if (Math.abs(a[i]-a[n])==Math.abs(i-n) || a[i]==a[n])                return false;        }        return true;    }}时间复杂度:O(n!)
空间复杂度:O(n+x)
-----------------------------------未完-----------------------------------
后面还有一个八皇后II,其实也就是大同小异,上面的是打印出棋盘,这个II就是计算个数(??这特么有啥区别?)
所以直接贴代码了。
递归实现:
public int totalNQueens(int n) {    Set columns = new HashSet();    Set diagonals1 = new HashSet();    Set diagonals2 = new HashSet();    return backtrack(n, 0, columns, diagonals1, diagonals2);}public int backtrack(int n, int row, Set columns, Set diagonals1, Set diagonals2) {    if (row == n) {        return 1;    } else {        int count = 0;        for (int i = 0; i < n; i++) {            if (columns.contains(i)) {                continue;            }            int diagonal1 = row - i;            if (diagonals1.contains(diagonal1)) {                continue;            }            int diagonal2 = row + i;            if (diagonals2.contains(diagonal2)) {                continue;            }            columns.add(i);            diagonals1.add(diagonal1);            diagonals2.add(diagonal2);            count += backtrack(n, row + 1, columns, diagonals1, diagonals2);            columns.remove(i);            diagonals1.remove(diagonal1);            diagonals2.remove(diagonal2);        }        return count;    }}时间复杂度:O(n的n次方)
空间复杂度:O(n)
非递归实现:
public static int totalNQueens(int n) {    int count = 0, i = 1;    // 用数组a存储棋子坐标,可以理解为i代表列,a[i]代表行    int[] a = new int[n+1];    while (i > 0) {        // i为当前列,寻找前面各列与当前第i列的排斥情况,拿到的a[i]就是当前行i的合适a[i]列        for (a[i]++; a[i]<=n; a[i]++) if (check2(a, i)) break;        // 如果a[i]列小于n,则可以继续向后找        if (a[i] <= n) {            // 如果当前行i就是第n行,则数量加1            if (i == n) {                count++;            // 否则就是向后一列找,并且后面一列无论是有没找过都要重置为0;            } else {                i++;                a[i] = 0;            }        // 否则就是回溯,回到前一列(然后继续向下面行找)        } else {            i--;        }    }    return count;}private static boolean check2(int[] a, int n) {    for (int i=1; i        if (Math.abs(a[i]-a[n])==Math.abs(i-n) || a[i]==a[n])            return false;    }    return true;}时间复杂度:O(n!)
空间复杂度:O(n)
需要注意的是,递归的回溯法是一颗全部展开的树,时间复杂度是N的N次方,很灵恐怖,虽然好理解,但是还是建议用迭代法。
--------------------------------------------完--------------------------------------------
当我望向你的时候,多希望你也在看着我。