简介
N皇后问题分析:
- 时间复杂度:O(n!) ,其实如果看树形图的话,直觉上是O(n^n),但皇后之间不能见面所以在搜索的过程中是有剪枝的,最差也就是O(n!),n!表示n (n-1) …. * 1。
 - 空间复杂度:O(n),和子集问题同理。
 
解数独问题分析:
- 时间复杂度:O(9^m) , m是’.’的数目。
 - 
51. N 皇后
题目描述
解题思路
 递归函数参数
我依然是定义全局变量二维数组result来记录最终结果。
参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。
- 递归终止条件:
 
可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。
- 单层搜索的逻辑
 
递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
验证棋盘是否合法
按照如下标准去重:
- 不能同行
 - 不能同列
 - 不能同斜线 (45度和135度角)
 

public List<List<String>> solveNQueens(int n) {if (n == 0) {return res;}// 需要将二维数组全部赋值,防止出现空字符char[][] chars = new char[n][n];for (char[] num : chars) {Arrays.fill(num, '.'); // 将指定的char值分配给指定的char数组的每个元素}backtracking(n, 0, chars);return res;}List<List<String>> res = new ArrayList<>();*//*** 回溯搜索** @param n 表示输入的棋盘大小* @param row 表示当前遍历到第几行了* @param chessboard 表示棋盘( ‘.’ 表示空格,‘Q’表示皇后)*/public void backtracking(int n, int row, char[][] chessboard) {if (row == n) {res.add(numToList(chessboard));// 此行return;}for (int col = 0; col < n; col++) { // for遍历每列if (isValid(row, col, n, chessboard)) {chessboard[row][col] = 'Q'; // 放置皇后backtracking(n, row + 1, chessboard); // 递归遍历每行,注意row需要加一chessboard[row][col] = '.'; // 回溯}}}/*** 二维字符数组转化为List集合** @param chessboard* @return*/public List<String> numToList(char[][] chessboard) {List<String> list = new ArrayList<>();for (char[] num : chessboard) {list.add(String.valueOf(num));}return list;}/*** 判断是否满足n皇后的条件** @param row* @param col* @param chessboard* @return*/public boolean isValid(int row, int col, int n, char[][] chessboard) {// 判断一列一列是否有皇后for (int i = 0; i < row; i++) {if (chessboard[i][col] == 'Q')return false;}// 判断135度是否有皇后for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if (chessboard[i][j] == 'Q')return false;}// 判断45度是否有皇后for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {if (chessboard[i][j] == 'Q')return false;}return true;}
在这份代码中,细心的同学可以发现为什么没有在同行进行检查呢?
因为在单层搜索的过程中,每一层递归,只会选for循环(也就是同一行)里的一个元素,所以不用去重了。
52. N皇后 II
题目描述
解题思路
和上一题求解方式一直,但取结果时只取多少种。
class Solution {public int totalNQueens(int n) {char[][] chessboard = new char[n][n];for (int i = 0; i < n; i++) {Arrays.fill(chessboard[i], '.');}backtracking(n, 0, chessboard);return result;}int result = 0;public boolean backtracking(int n, int row, char[][] chessboard) {if (row == n) {result++;return true;}for (int i = 0; i < n; i++) {if (isValid(chessboard, n, row, i)) {chessboard[row][i] = 'Q';backtracking(n, row + 1, chessboard);chessboard[row][i] = '.';}}return false;}public boolean isValid(char[][] chessboard, int n, int row, int col) {int i = 0, j = 0;for (i = 0; i < row; i++) {if (chessboard[i][col] == 'Q') {return false;}}for (i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {if (chessboard[i][j] == 'Q') {return false;}}for (i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {if (chessboard[i][j] == 'Q') {return false;}}return true;}}
37. 解数独
题目描述
解题思路
N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来来遍历列,然后一行一列确定皇后的唯一位置。
本题就不一样了,本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。
- 递归函数以及参数
 
递归函数的返回值需要是bool类型,为什么呢?
因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值,这一点在N皇后问题中已经介绍过了,一样的道理。
- 递归终止条件
 
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
不用终止条件会不会死循环?
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
那么有没有永远填不满的情况呢?
这个问题我在递归单层搜索逻辑里在来讲!
- 递归单层搜索逻辑
 
在树形图中可以看出我们需要的是一个二维的递归(也就是两个for循环嵌套着递归)
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!如果9个数字都不能填充进去,那么返回false,这就是没用终止条件也不会永远填不满棋盘而无限递归下去!
判断棋盘是否合法
判断棋盘是否合法有如下三个维度:
- 同行是否重复
 - 同列是否重复
 - 9宫格里是否重复
 

public void solveSudoku(char[][] board) {if (board == null) {return;}backtracking(board);}public boolean backtracking(char[][] board) {// 可以不需要返回条件,不符合会返回false// 遍历整个二维数组for (int i = 0; i < board.length; i++) {for (int j = 0; j < board[0].length; j++) {if (board[i][j] != '.') continue; // 此时位置已经有数字,跳过for (char k = '1'; k <= '9'; k++) { // 依次判断数组 1~9if (isValid(i, j, k, board)) {board[i][j] = k;if (backtracking(board)) return true; // 找到一条就返回board[i][j] = '.';}}return false; // 如果9个数字都不满足条件,那么此处此数独无解}}return true; // 所有都遍历完没有返回false,满足条件}/*** 判断一行,一列,以及九宫格中是否有重复的数字** @param row* @param col* @param val* @param board* @return*/public boolean isValid(int row, int col, char val, char[][] board) {// 判断一行for (int i = 0; i < board[row].length; i++) {if (board[row][i] == val) {return false;}}// 判断一列for (int i = 0; i < board.length; i++) {if (board[i][col] == val) {return false;}}// 判断九宫格int startRow = (row / 3) * 3; // 行起始位置int startCol = (col / 3) * 3; // 列起始位置for (int i = startRow; i < startRow + 3; i++) {for (int j = startCol; j < startCol + 3; j++) {if (board[i][j] == val) {return false;}}}return true;}
