n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 上图为 8 皇后问题的一种解法。 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。 示例:
输入:4 输出:[ [".Q…", // 解法 1 “…Q”, “Q…”, “…Q.”],
["…Q.", // 解法 2 “Q…”, “…Q”, “.Q…”] ] 解释: 4 皇后问题存在两个不同的解法。
提示:
皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/n-queens 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
经典回溯算法,一开始还担心会不会时间不过后来发现是想多了。总之,用一个二维数组记录已经被占据的点位,一行的皇后位置暂定之后开始找下一行可行的皇后位置;每一行内部一个迭代:
class Solution { List<List<String>> ans; int[] array_ans; int[][] occupied; void occupy(int row, int col, int add, int n) { int tRow = row, tCol = col; while(tRow < n) { occupied[tRow][tCol] += add; ++tRow; } tRow = row; while(tCol >= 0 && tRow < n) { occupied[tRow][tCol] += add; ++tRow; --tCol; } tRow = row; tCol = col; while(tCol < n && tRow < n) { occupied[tRow][tCol] += add; ++tRow; ++tCol; } } void arrToAns(int n) { List<String> cur = new LinkedList<>(); StringBuilder builder = new StringBuilder(); for(int row = 0; row < n; ++row) { builder.setLength(0); int col = array_ans[row]; for(int i = 0; i < n; ++i) { builder.append(i == col ? 'Q' : '.'); } cur.add(builder.toString()); } ans.add(cur); } void tryRow(int row, int n) { for(int col = 0; col < n; ++col) { if(occupied[row][col] == 0) { array_ans[row] = col; if(row == n - 1) { arrToAns(n); } else { occupy(row, col, 1, n); tryRow(row + 1, n); occupy(row, col, -1, n); } } } } public List<List<String>> solveNQueens(int n) { ans = new LinkedList<>(); array_ans = new int[n]; occupied = new int[n][n]; tryRow(0, n); return ans; } }看看题解有没有什么优化的方法。首先,其实判断是否被占领只需要三个整形数组就好了,分别表示列、左上右下对角线和右上左下对角线是否被占领。然后就是很无聊的用原始的代替java封装好的的优化:
class Solution { List<List<String>> ans; int[] array_ans; int[] colOccupied; int[] l2rOccupied; int[] r2lOccupied; boolean isNotOccupied(int row, int col, int n) { return colOccupied[col] == 0 && l2rOccupied[row - col + n - 1] == 0 && r2lOccupied[row + col] == 0; } void occupy(int row, int col, int add, int n) { colOccupied[col] += add; l2rOccupied[row - col + n - 1] += add; r2lOccupied[row + col] += add; } void arrToAns(int n) { List<String> cur = new LinkedList<>(); char[] builder = new char[n]; for(int row = 0; row < n; ++row) { int col = array_ans[row]; for(int i = 0; i < n; ++i) { builder[i] = (i == col ? 'Q' : '.'); } cur.add(String.valueOf(builder)); } ans.add(cur); } void tryRow(int row, int n) { for(int col = 0; col < n; ++col) { if(isNotOccupied(row, col, n)) { array_ans[row] = col; if(row == n - 1) { arrToAns(n); } else { occupy(row, col, 1, n); tryRow(row + 1, n); occupy(row, col, -1, n); } } } } public List<List<String>> solveNQueens(int n) { //回溯看看时间给不给过 ans = new LinkedList<>(); array_ans = new int[n]; colOccupied = new int[n]; l2rOccupied = new int[2 * n - 1]; r2lOccupied = new int[2 * n - 1]; tryRow(0, n); return ans; } }4ms变成了3ms,不错。 题解还给出了一种用位代表对角线是否被占领的方法,但是这种方法并没有节省空间,因为用位表示上面的int[]可以得到相同的空间效果(其实能省n-1bit)…还有就是这种方法需要来回传递表示对角线状况的数,其实更繁琐一点…懒得实现了
另外,翻了下自己一年前第一遍做这个的代码,发现有一个优化。对第一行只搜索前一半,输出答案的时候如果在前一半就输出一个对称的:
class Solution { List<List<String>> ans; int[] array_ans; int[] colOccupied; int[] l2rOccupied; int[] r2lOccupied; boolean isNotOccupied(int row, int col, int n) { return colOccupied[col] == 0 && l2rOccupied[row - col + n - 1] == 0 && r2lOccupied[row + col] == 0; } void occupy(int row, int col, int add, int n) { colOccupied[col] += add; l2rOccupied[row - col + n - 1] += add; r2lOccupied[row + col] += add; } void arrToAns(int n) { List<String> cur = new LinkedList<>(); char[] builder = new char[n]; for(int row = 0; row < n; ++row) { int col = array_ans[row]; for(int i = 0; i < n; ++i) { builder[i] = (i == col ? 'Q' : '.'); } cur.add(String.valueOf(builder)); } ans.add(cur); cur = new LinkedList<>(); if(array_ans[0] < n / 2) { //输出一个对称的 for(int row = 0; row < n; ++row) { int col = array_ans[row]; for(int i = 0; i < n; ++i) { builder[n - i - 1] = (i == col ? 'Q' : '.'); } cur.add(String.valueOf(builder)); } ans.add(cur); } } void tryRow(int row, int n) { int maxCol = row == 0 ? (n + 1) / 2 : n; for(int col = 0; col < maxCol; ++col) { if(isNotOccupied(row, col, n)) { array_ans[row] = col; if(row == n - 1) { arrToAns(n); } else { occupy(row, col, 1, n); tryRow(row + 1, n); occupy(row, col, -1, n); } } } } public List<List<String>> solveNQueens(int n) { //回溯看看时间给不给过 ans = new LinkedList<>(); array_ans = new int[n]; colOccupied = new int[n]; l2rOccupied = new int[2 * n - 1]; r2lOccupied = new int[2 * n - 1]; tryRow(0, n); return ans; } }嗯,变成了2ms,这可是82%和95%的区别啊!!!顺便我一年前写的纯迭代的回溯还蛮可爱的。也贴在这把纪念一下:
class Solution { public: void printAns(vector<vector<string>> & ans, int * pAns, int n) { string s; vector<string> temp; //第一个是不管咋样都需要打印的 for (int j = 0; j < n; j++) s.push_back('.'); for (int i = 0; i < n; i++) temp.push_back(s); ans.push_back(temp); for (int i = 0; i < n; i++) ans.back()[i][pAns[i]] = 'Q'; //从数组打印答案 第一行的数是0到n/2 - 1时打印俩 if (pAns[0] < n / 2) { //打印俩 ans.push_back(temp); for (int i = 0; i < n; i++) ans.back()[i][n - 1 - pAns[i]] = 'Q'; } } vector<vector<string>> solveNQueens(int n) { if (n == 1) return{ { "Q" } }; if (n < 4) return{}; vector<vector<string>> ans; //想不出来好方法暴力试凑 bool * pvCol = new bool[n];//特定列是否被占 bool * pvLCross = new bool[n * 2 - 1];//左向右对角线 索引计算col - row + n - 1 bool * pvRCross = new bool[n * 2 - 1];//索引计算col + row int * pAns = new int[n]; for (int i = 0; i < n; i++) pAns[i] = pvCol[i] = 0; for (int i = 0; i < n * 2 - 1; i++) pvLCross[i] = pvRCross[i] = 0; int row = 0, col = 0; while (pAns[0] <= (n + 1) / 2 - 1)//只用考虑左边一半 { //cout << "row = " << row << endl; //cout << "col = " << col << endl << endl; if (col == n)//下一行没戏了 { row--; col = pAns[row]; pvCol[col] = false; pvLCross[col - row + n - 1] = false; pvRCross[col + row] = false;//清空当前位置 col++; continue; } if (pvCol[col] || pvLCross[col - row + n - 1] || pvRCross[col + row])//如果当前位置不可行 { col++; continue; } else { pAns[row] = col; if (row != n - 1)//当前位置可行 找下一行 { pvCol[col] = true; pvLCross[col - row + n - 1] = true; pvRCross[col + row] = true;//装上当前位置 row++; col = 0; } else//找到一组答案 { printAns(ans, pAns, n); row--; col = pAns[row]; pvCol[col] = false; pvLCross[col - row + n - 1] = false; pvRCross[col + row] = false;//清空当前位置 col++; } } } delete[] pvCol; delete[] pvLCross; delete[] pvRCross; delete[] pAns; return ans; } };