332. 重新安排行程
自己写的话暴力全排列了,没用用到图结构来简化路径搜索。感觉理论上能解决,提交后超时。
记录一下深度优先搜索的图算法:
from collections import defaultdict
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
res = []
graph = defaultdict(list)
for src,dst in sorted(tickets, reverse=True):
graph[src].append(dst)
def backtrack(airport):
while graph[airport]:
next_airport = graph[airport].pop()
backtrack(next_airport)
res.append(airport)
backtrack('JFK')
return res[::-1]
51. N 皇后
大一第一学期,python程序设计课的课程作业... 都已经9年了吗..为什么我还是这么菜!
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def backtrack(row):
if row == n:
board = build_board()
result.append(board)
return
for col in range(n):
if col in cols or (row + col) in diag1 or (row - col) in diag2:
continue
cols.add(col)
diag1.add(row + col)
diag2.add(row - col)
queens[row] = col
backtrack(row + 1)
cols.remove(col)
diag1.remove(row + col)
diag2.remove(row - col)
def build_board():
board = []
for i in range(n):
row = ['.'] * n
row[queens[i]] = 'Q'
board.append(''.join(row))
return board
result = []
queens = [-1] * n # 记录每行皇后的列位置
cols = set() # 记录已经放置皇后的列
diag1 = set() # 记录主对角线上的位置(r + c)
diag2 = set() # 记录副对角线上的位置(r - c)
backtrack(0)
return result
37. 解数独
同样也是期末课程设计的选题之一...
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
rows = [set(range(1, 10)) for _ in range(9)] # 每行可用数字
cols = [set(range(1, 10)) for _ in range(9)] # 每列可用数字
boxes = [set(range(1, 10)) for _ in range(9)] # 每个宫格可用数字
empty = [] # 记录空格位置
# 初始化
for i in range(9):
for j in range(9):
if board[i][j] != '.':
val = int(board[i][j])
rows[i].remove(val)
cols[j].remove(val)
boxes[(i // 3) * 3 + j // 3].remove(val)
else:
empty.append((i, j))
def backtrack(iter=0):
if iter == len(empty): # 所有空格已填完
return True
i, j = empty[iter]
b = (i // 3) * 3 + j // 3
for val in rows[i] & cols[j] & boxes[b]:
rows[i].remove(val)
cols[j].remove(val)
boxes[b].remove(val)
board[i][j] = str(val)
if backtrack(iter + 1):
return True
rows[i].add(val)
cols[j].add(val)
boxes[b].add(val)
board[i][j] = '.'
return False
backtrack()
总结:
回溯算法的本质
回溯算法本质上是一种通过递归来实现深度优先搜索(DFS)的算法。它试图在每一步做出选择,然后继续向前探索,如果发现当前选择并不是一个正确的解或者是一个最优解,它会撤销这个选择(也就是所谓的“回溯”),然后尝试其他的选项。
应用场景
回溯算法可以应用于多种问题,包括但不限于:
- 组合问题:从N个数中选出k个数的所有可能组合。
- 排列问题:N个数的所有排列方式。
- 切割问题:如将字符串切割成回文串的所有可能方式。
- 子集问题:求一个集合的所有子集。
- 棋盘问题:如N皇后问题,解数独等。
解题模板
回溯算法有一个通用的解题模板,基本上包括以下几个步骤:
- 路径:已经做出的选择。
- 选择列表:当前可以做的选择。
- 结束条件:到达决策树底层,无法再做选择的条件。
代码模板大致如下:
void backtrack(路径, 选择列表) {
if (满足结束条件) {
存放结果;
return;
}
for (选择 : 选择列表) {
做选择;
backtrack(路径, 选择列表);
撤销选择;
}
}
剪枝优化
在实际应用中,为了提高回溯算法的效率,常常需要进行剪枝操作,即在递归过程中提前排除那些明显不会得到正确解的路径,从而减少不必要的计算。