51. N皇后
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
这道题目的难点:之前我们用回溯解决多了组合、切割、子集、排列问题,这些问题给的都是一个一维的集合,但是该题我们要在二维矩阵上进行搜索。
首先来看一下皇后们的约束条件:
- 不能同行
- 不能同列
- 不能同斜线
这也就告诉我们:每一行肯定会有一个皇后,每一列肯定也会有一个皇后
一个暴利的想法就是嵌套n个for循环,遍历每一行的每一个位置,但是随着n的增多,我们就写不出来了,所以我们就要回溯,这样可以用递归的方式来帮助我们控制for循环的层数,每次递归都是在遍历新的一行
确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。(我们做过的所有题都是抽象为了一棵树,可见先抽象为一棵树是我们解题的关键)
下面用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:
难点:① 如何遍历放置皇后的位置 ②判断在某个位置放置皇后是否有效
回溯三部曲
- 递归函数参数
依然是定义全局变量二维数组result来记录最终结果。
参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。
- 递归终止条件
当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。
- 单层搜索的逻辑
递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
- 验证棋盘是否合法
按照如下标准去重:
- 不能同行
- 不能同列
- 不能同斜线 (45度和135度角)
class Solution:
def __init__(self):
self.result = []
def solveNQueens(self, n: int) -> List[List[str]]:
def backtracking(n,row,chessboard):
if row == n:
self.result.append(chessboard[:])
for i in range(0,n):
if self.isVaild(row,i,chessboard):
chessboard[row] = chessboard[row][:i] + 'Q' + chessboard[row][i+1:] # 放置皇后
backtracking(n,row+1,chessboard)
#回溯
chessboard[row] = chessboard[row][:i] + '.' + chessboard[row][i+1:]
chessboard = ['.' * n for _ in range(n)] # 初始化棋盘
print(chessboard)
backtracking(n,0,chessboard)
return [[''.join(row) for row in solution] for solution in self.result]
def isVaild(self,row,col,chessboard):
#检验列
for _ in range(row):
if chessboard[_][col] == 'Q':
return False # 当前列已经存在皇后,不合法
# 检查 45 度角是否有皇后
i, j = row - 1,col - 1 #i和形参有重复,下面改变导致出现混乱
while i >= 0 and j >= 0:
if chessboard[i][j] == 'Q':
return False # 左上方向已经存在皇后,不合法
i -= 1
j -= 1
# 检查 135 度角是否有皇后
i, j = row - 1, col + 1
while i >= 0 and j < len(chessboard):
if chessboard[i][j] == 'Q':
return False # 右上方向已经存在皇后,不合法
i -= 1
j += 1
return True
37. 解数独
文档讲解:代码随想录
题目链接:. - 力扣(LeetCode)
难点: 本题与上一题的区别是,上一题只要选中一个格子就是一种情况,但是本题选中一个格子之后还要判断在格子中放哪个数字(0-9),比上一题多一个维度;并且只要找到符合条件的就可以返回,所以回溯函数有bool返回值
回溯三部曲
- 递归函数以及参数
递归函数的返回值需要是bool类型,为什么呢?
因为解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
- 递归终止条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
不用终止条件会不会死循环?
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
- 递归单层搜索逻辑
在树形图中可以看出我们需要的是一个二维的递归 (一行一列)
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
self.backtracking(board)
def backtracking(self, board: List[List[str]]) -> bool:
# 若有解,返回True;若无解,返回False
for i in range(len(board)): # 遍历行
for j in range(len(board[0])): # 遍历列
# 若空格内已有数字,跳过
if board[i][j] != '.': continue
for k in range(1, 10):
if self.is_valid(i, j, k, board):
board[i][j] = str(k)
if self.backtracking(board): return True
board[i][j] = '.'
# 若数字1-9都不能成功填入空格,返回False无解
return False
return True # 有解
def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:
# 判断同一行是否冲突
for i in range(9):
if board[row][i] == str(val):
return False
# 判断同一列是否冲突
for j in range(9):
if board[j][col] == str(val):
return False
# 判断同一九宫格是否有冲突
start_row = (row // 3) * 3
start_col = (col // 3) * 3
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == str(val):
return False
return True