|
| 1 | +# N皇后 |
| 2 | +[51. N皇后](https://leetcode-cn.com/problems/n-queens/) |
| 3 | +### 题目描述 |
| 4 | +> n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 |
| 5 | +> |
| 6 | + |
| 7 | +> |
| 8 | +上图为 8 皇后问题的一种解法。 |
| 9 | +> |
| 10 | +给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 |
| 11 | +> |
| 12 | +每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 |
| 13 | + |
| 14 | +示例: |
| 15 | + |
| 16 | +``` |
| 17 | +输入: 4 |
| 18 | +输出: [ |
| 19 | + [".Q..", // 解法 1 |
| 20 | + "...Q", |
| 21 | + "Q...", |
| 22 | + "..Q."], |
| 23 | +
|
| 24 | + ["..Q.", // 解法 2 |
| 25 | + "Q...", |
| 26 | + "...Q", |
| 27 | + ".Q.."] |
| 28 | +] |
| 29 | +解释: 4 皇后问题存在两个不同的解法。 |
| 30 | +``` |
| 31 | + |
| 32 | +### 问题分析 |
| 33 | +约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。 |
| 34 | + |
| 35 | +使用一维数组表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。 |
| 36 | +每行只存储一个元素,然后递归到下一行,这样就不用判断行了,只需要判断列和对角线。 |
| 37 | +### Solution1 |
| 38 | +当result[row] = column时,即row行的棋子在column列。 |
| 39 | + |
| 40 | +对于[0, row-1]的任意一行(i 行),若 row 行的棋子和 i 行的棋子在同一列,则有result[i] == column; |
| 41 | +若 row 行的棋子和 i 行的棋子在同一对角线,等腰直角三角形两直角边相等,即 row - i == Math.abs(result[i] - column) |
| 42 | + |
| 43 | +布尔类型变量 isValid 的作用是剪枝,减少不必要的递归。 |
| 44 | +``` |
| 45 | +public List<List<String>> solveNQueens(int n) { |
| 46 | + // 下标代表行,值代表列。如result[0] = 3 表示第1行的Q在第3列 |
| 47 | + int[] result = new int[n]; |
| 48 | + List<List<String>> resultList = new LinkedList<>(); |
| 49 | + dfs(resultList, result, 0, n); |
| 50 | + return resultList; |
| 51 | +} |
| 52 | +
|
| 53 | +void dfs(List<List<String>> resultList, int[] result, int row, int n) { |
| 54 | + // 递归终止条件 |
| 55 | + if (row == n) { |
| 56 | + List<String> list = new LinkedList<>(); |
| 57 | + for (int x = 0; x < n; ++x) { |
| 58 | + StringBuilder sb = new StringBuilder(); |
| 59 | + for (int y = 0; y < n; ++y) |
| 60 | + sb.append(result[x] == y ? "Q" : "."); |
| 61 | + list.add(sb.toString()); |
| 62 | + } |
| 63 | + resultList.add(list); |
| 64 | + return; |
| 65 | + } |
| 66 | + for (int column = 0; column < n; ++column) { |
| 67 | + boolean isValid = true; |
| 68 | + result[row] = column; |
| 69 | + /* |
| 70 | + * 逐行往下考察每一行。同列,result[i] == column |
| 71 | + * 同对角线,row - i == Math.abs(result[i] - column) |
| 72 | + */ |
| 73 | + for (int i = row - 1; i >= 0; --i) { |
| 74 | + if (result[i] == column || row - i == Math.abs(result[i] - column)) { |
| 75 | + isValid = false; |
| 76 | + break; |
| 77 | + } |
| 78 | + } |
| 79 | + if (isValid) dfs(resultList, result, row + 1, n); |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | +### Solution2 |
| 84 | +使用LinkedList表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。 |
| 85 | + |
| 86 | +解法二和解法一的不同在于,相同列以及相同对角线的校验。 |
| 87 | +将对角线抽象成【一次函数】这个简单的数学模型,根据一次函数的截距是常量这一特性进行校验。 |
| 88 | + |
| 89 | +这里,我将右上-左下对角线,简称为“\”对角线;左上-右下对角线简称为“/”对角线。 |
| 90 | + |
| 91 | +“/”对角线斜率为1,对应方程为y = x + b,其中b为截距。 |
| 92 | +对于线上任意一点,均有y - x = b,即row - i = b; |
| 93 | +定义一个布尔类型数组anti_diag,将b作为下标,当anti_diag[b] = true时,表示相应对角线上已经放置棋子。 |
| 94 | +但row - i有可能为负数,负数不能作为数组下标,row - i 的最小值为-n(当row = 0,i = n时),可以加上n作为数组下标,即将row -i + n 作为数组下标。 |
| 95 | +row - i + n 的最大值为 2n(当row = n,i = 0时),故anti_diag的容量设置为 2n 即可。 |
| 96 | + |
| 97 | + |
| 98 | + |
| 99 | +“\”对角线斜率为-1,对应方程为y = -x + b,其中b为截距。 |
| 100 | +对于线上任意一点,均有y + x = b,即row + i = b; |
| 101 | +同理,定义数组main_diag,将b作为下标,当main_diag[row + i] = true时,表示相应对角线上已经放置棋子。 |
| 102 | + |
| 103 | +有了两个校验对角线的数组,再来定义一个用于校验列的数组cols,这个太简单啦,不解释。 |
| 104 | + |
| 105 | +**解法二时间复杂度为O(n!),在校验相同列和相同对角线时,引入三个布尔类型数组进行判断。相比解法一,少了一层循环,用空间换时间。** |
| 106 | + |
| 107 | +``` |
| 108 | +List<List<String>> resultList = new LinkedList<>(); |
| 109 | +
|
| 110 | +public List<List<String>> solveNQueens(int n) { |
| 111 | + boolean[] cols = new boolean[n]; |
| 112 | + boolean[] main_diag = new boolean[2 * n]; |
| 113 | + boolean[] anti_diag = new boolean[2 * n]; |
| 114 | + LinkedList<Integer> result = new LinkedList<>(); |
| 115 | + dfs(result, 0, cols, main_diag, anti_diag, n); |
| 116 | + return resultList; |
| 117 | +} |
| 118 | +
|
| 119 | +void dfs(LinkedList<Integer> result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) { |
| 120 | + if (row == n) { |
| 121 | + List<String> list = new LinkedList<>(); |
| 122 | + for (int x = 0; x < n; ++x) { |
| 123 | + StringBuilder sb = new StringBuilder(); |
| 124 | + for (int y = 0; y < n; ++y) |
| 125 | + sb.append(result.get(x) == y ? "Q" : "."); |
| 126 | + list.add(sb.toString()); |
| 127 | + } |
| 128 | + resultList.add(list); |
| 129 | + return; |
| 130 | + } |
| 131 | + for (int i = 0; i < n; ++i) { |
| 132 | + if (cols[i] || main_diag[row + i] || anti_diag[row - i + n]) |
| 133 | + continue; |
| 134 | + result.add(i); |
| 135 | + cols[i] = true; |
| 136 | + main_diag[row + i] = true; |
| 137 | + anti_diag[row - i + n] = true; |
| 138 | + dfs(result, row + 1, cols, main_diag, anti_diag, n); |
| 139 | + result.removeLast(); |
| 140 | + cols[i] = false; |
| 141 | + main_diag[row + i] = false; |
| 142 | + anti_diag[row - i + n] = false; |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
0 commit comments