说明:问题描述来源leetcode
一、问题描述:
51. N 皇后
难度困难1592
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
提示:
1 <= n <= 9
二、题解:
做题过程:
题解1:
class Solution {
List<List<String>> res = new LinkedList<>();
char[][] chars ;
private int n;
public List<List<String>> solveNQueens(int n) {
chars= new char[n][n];
this.n = n;
backTracking(0,0,0);
return res;
}
private void backTracking(int row, int column,int count) {
if (count == n){
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes();
return;
}
for (int i = row; i < n; i++) {
for (int j = column; j < n; j++) {
if (chars[i][j] == 0){
char[][] arrays = getNewArrays();
doIAndJ(i,j);
count++;
backTracking(i + 1,0,count);
count--;
chars = arrays;
}
}
}
}
private void addToRes() {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
private char[][] getNewArrays() {
char[][] result = new char[n][n];
for (int i = 0; i < chars.length; i++) {
for (int j = 0; j < chars[i].length; j++) {
result[i][j] = chars[i][j];
}
}
return result;
}
private void doIAndJ(int row, int column) {
chars[row][column] = 'Q';
for (int i = 0; i < n; i++) {
if (chars[row][i] == 0) chars[row][i] = '.';
if (chars[i][column] == 0) chars[i][column] = '.';
if (column - i >= 0){//左边
if (row - i >=0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上
if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下
}
if (column + i < n){//右边
if (row - i >=0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上
if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下
}
}
}
}
这个可以通过n为4的情况
后面这个题解通过了!!!
居然通过了哈哈!
还以为会超出时间复杂度。
还可以继续优化:
题解2:
再分析:
每一行必定会有一个皇后。
为什么?我们拆解这句话应该是等价于:每一行最多只可以有一个皇后,最少也是只有一个皇后。
先证明前半部分每一行最多只可以有一个皇后
,①反证法:假如存在一行有2个皇后,那么不就和题目所说的同一行的皇后会相互攻击的条件所矛盾了吗?
再证明后半部分最少也是只有一个皇后
,②反证法:假如最少是0个,那么存在一行没有皇后,那么肯定会有某一行会有多个皇后,这不就又回到①了吗
因此每一行必定有一个皇后。也不知道证明得够不够说服力,目前就感觉证明得没什么问题。
那么当我们即将遍历到n+1行就退出即可。
相应的,根据对称性,每一列也必定会存在一个皇后。
找到了这个条件,我们可以改造下:
class Solution {
List<List<String>> res = new LinkedList<>();
char[][] chars;
private int n;
public List<List<String>> solveNQueens(int n) {
chars = new char[n][n];
this.n = n;
backTracking(0);
return res;
}
private void backTracking(int row) {
if (row == n) {
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes();
return;
}
for (int i = 0; i < n; i++) {//对于每一列
if (chars[row][i] == 0) {
char[][] arrays = getNewArrays();
doIAndJ(row,i);
backTracking(row + 1);
chars = arrays;
}
}
}
private void addToRes() {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
private char[][] getNewArrays() {
char[][] result = new char[n][n];
for (int i = 0; i < chars.length; i++) {
for (int j = 0; j < chars[i].length; j++) {
result[i][j] = chars[i][j];
}
}
return result;
}
private void doIAndJ(int row, int column) {
chars[row][column] = 'Q';
for (int i = 0; i < n; i++) {
if (chars[row][i] == 0) chars[row][i] = '.';
if (chars[i][column] == 0) chars[i][column] = '.';
if (column - i >= 0) {//左边
if (row - i >= 0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上
if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下
}
if (column + i < n) {//右边
if (row - i >= 0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上
if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下
}
}
}
}
end
这里的效率较之题解1已经提高了很多了,剩下的应该是对数据的处理的部分的优化了。
而题解1和题解2相比较,题解1走多了什么步骤呢?题解1有2层循环,我们可以设想当第一层递归遍历到第二行的chars数组时,是不是已经算是找到了所有结果了呢?
事实上按照推理是这样的,当第一行全部遍历后已经是找到所有的结果了,而其他的递归也存在遍历第二次的场景。
也就是当那一行确定后,再遍历到下一行进行递归时就已经出现了重复了,不行可以改一下题解1的代码,让它每次递归都不走下一行的遍历:
class Solution {
List<List<String>> res = new LinkedList<>();
char[][] chars ;
private int n;
public List<List<String>> solveNQueens(int n) {
chars= new char[n][n];
this.n = n;
backTracking(0,0,0);
return res;
}
private void backTracking(int row, int column,int count) {
if (count == n){
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes();
return;
}
for (int i = row; i < n; i++) {
for (int j = column; j < n; j++) {
if (chars[i][j] == 0){
char[][] arrays = getNewArrays();
doIAndJ(i,j);
count++;
backTracking(i + 1,0,count);
count--;
chars = arrays;
}
}
break;
}
}
private void addToRes() {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
private char[][] getNewArrays() {
char[][] result = new char[n][n];
for (int i = 0; i < chars.length; i++) {
for (int j = 0; j < chars[i].length; j++) {
result[i][j] = chars[i][j];
}
}
return result;
}
private void doIAndJ(int row, int column) {
chars[row][column] = 'Q';
for (int i = 0; i < n; i++) {
if (chars[row][i] == 0) chars[row][i] = '.';
if (chars[i][column] == 0) chars[i][column] = '.';
if (column - i >= 0){//左边
if (row - i >=0 && chars[row - i][column - i] == 0) chars[row - i][column - i] = '.';//左上
if (row + i < n && chars[row + i][column - i] == 0) chars[row + i][column - i] = '.';//左下
}
if (column + i < n){//右边
if (row - i >=0 && chars[row - i][column + i] == 0) chars[row - i][column + i] = '.';//左上
if (row + i < n && chars[row + i][column + i] == 0) chars[row + i][column + i] = '.';//左下
}
}
}
}
这里和题解1的区别就是只完成内部的一层遍历就break了,无法继续走下面的行的遍历,效率和题解2几乎一样。
我们剩下的就对题解2进行优化数据处理就可以了:
但是本题解2的数据处理部分很难处理,这里对数据的操作实在是够多了的,比如getNewArrays
这个函数每次都要创建和修改,其他的也是,总之很难修改了,是因为选定了默认(数值表示为0)的char作为判断条件.
可以换另一种思路,
比如说让是否为目标值作为判断条件。
题解3:
下面是替换了要判断的目标值,同时对数据的操作也减少了,不需要很多次地进行数组的创建
class Solution {
List<List<String>> res = new LinkedList<>();
char[][] chars;
private int n;
public List<List<String>> solveNQueens(int n) {
chars = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(chars[i],'.');
}
this.n = n;
backTracking(0);
return res;
}
private void backTracking(int row) {
if (row == n) {
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes();
return;
}
for (int i = 0; i < n; i++) {//对于每一列
if (!isExistQ(row,i)){
chars[row][i] = 'Q';
backTracking(row + 1);
chars[row][i] = '.';
}
}
}
private boolean isExistQ(int row, int column) {
for (int i = 0; i < n; i++) {//判断列
if ( chars[i][column] == 'Q') return true;
}
for (int i = 1; i < n; i++) {
if (column - i >= 0) {//左边
if (row - i >= 0 && chars[row - i][column - i] == 'Q') return true;//左上
if (row + i < n && chars[row + i][column - i] == 'Q') return true;//左下
}
if (column + i < n) {//右边
if (row - i >= 0 && chars[row - i][column + i] == 'Q') return true;//左上
if (row + i < n && chars[row + i][column + i] == 'Q') return true;//左下
}
}
return false;
}
private void addToRes() {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
}
但是还是不够!到底是哪里出错了呢,然后思考一下:
再来剪枝操作一下终于!!!
题解4:
对于斜边的操作其实只需要对左上角、右上角进行查询是否有目标值Q
就可以了
class Solution {
List<List<String>> res = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chars = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(chars[i], '.');
}
backTracking(chars, n, 0);
return res;
}
private void backTracking(char[][] chars, int n, int row) {
if (row == n) {
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes(chars);
return;
}
for (int i = 0; i < n; i++) {//对于每一列
if (!isExistQ(chars, n, row, i)) {
chars[row][i] = 'Q';
backTracking(chars, n, row + 1);
chars[row][i] = '.';
}
}
}
private boolean isExistQ(char[][] chars, int n, int row, int column) {
for (int i = 0; i < n; i++) {//判断列
if (chars[i][column] == 'Q') return true;
}
for (int i = column - 1, j = 1; i >=0 && row - j >= 0; i--, j++) {
if (chars[row - j][i] == 'Q') return true;//左上
}
for (int i = column + 1, j = 1; i < n && row - j >= 0; i++, j++) {
if (chars[row - j][i] == 'Q') return true;//左上
}
return false;
}
private void addToRes(char[][] chars) {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
}
这个就成功优化到了2ms,而这样子实际上还是可以优化的,对于列的目标值Q
的查询直接剔除在下方的那些就可以了
也就是对isExistQ
函数的列操作进行稍作改变:
class Solution {
List<List<String>> res = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chars = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(chars[i], '.');
}
backTracking(chars, n, 0);
return res;
}
private void backTracking(char[][] chars, int n, int row) {
if (row == n) {
// 说明已经达到了不会相互攻击的n皇后,可以通过char获取一个结果
addToRes(chars);
return;
}
for (int i = 0; i < n; i++) {//对于每一列
if (!isExistQ(chars, n, row, i)) {
chars[row][i] = 'Q';
backTracking(chars, n, row + 1);
chars[row][i] = '.';
}
}
}
private boolean isExistQ(char[][] chars, int n, int row, int column) {
for (int i = 0; i < row; i++) {//判断列
if (chars[i][column] == 'Q') return true;
}
for (int i = column - 1, j = 1; i >=0 && row - j >= 0; i--, j++) {
if (chars[row - j][i] == 'Q') return true;//左上
}
for (int i = column + 1, j = 1; i < n && row - j >= 0; i++, j++) {
if (chars[row - j][i] == 'Q') return true;//左上
}
return false;
}
private void addToRes(char[][] chars) {
List<String> list = new ArrayList<>();
for (int i = 0; i < chars.length; i++) {
String s = String.valueOf(chars[i]);
list.add(s);
}
res.add(new ArrayList<>(list));
}
}
end