导语
leetcode刷题笔记记录,本篇博客是回溯部分的最后一期,主要记录几个回溯部分的Hard难度的题目,包括:
Leetcode 332 重新安排行程
题目描述
给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]与["JFK", "LGB"]相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入: tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出: ["JFK","MUC","LHR","SFO","SJC"]
示例 2:
输入: tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出: ["JFK","ATL","JFK","SFO","ATL","SFO"]
解释: 另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠后。
提示:
1 <= tickets.length <= 300tickets[i].length == 2fromi.length == 3toi.length == 3fromi和toi由大写英文字母组成fromi != toi
解法
这是一个基于图的问题,因此需要创建一个图来表示每个机场和它们的所有可能的目的机场。然后使用回溯法进行深度优先搜索,寻找一个可行的路径。
主要的一些关键细节如下:
- 创建图:首先需要知道每个机场可以飞往哪些机场。因此,使用字典来创建一个图,其中键是出发机场,值是它可以到达的机场列表。这个列表按照字母排序,以便我们总是首先考虑字典顺序更小的机场。
- 回溯法:从JFK开始,尝试选择一个目的地。选择完一个目的地后,将其从图中的列表中移除,以表示这张机票已经被使用。然后,基于这个选择的目的地继续搜索下一步的选择。
- 目标与撤销:如果当前路径长度达到了机票总数 + 1,说明使用了所有的机票并找到了一个有效的行程。否则,我们需要撤销选择并尝试其他目的地。
- 结束搜索:一旦找到一个有效的行程,立即结束搜索。这是因为已经按照字典顺序排序了目的地,所以首先找到的行程一定是最小的。
这个题目还有一点别扭之处在于它是找到一个立马结束递归,所以像之前二叉树那边的题目一样,需要一个返回值来承接这一点。返回值在这里是为了方便我们“提前退出”。当我们在某个深度的递归中找到了合法行程,通过返回True来告诉上层的递归:“我已经找到了一个答案,你不需要继续尝试其他选择了。”这样,上层的递归也会返回True,一直向上,直到最外层的递归。如果不使用返回值,即使找到了一个答案,仍然会尝试所有的可能性,这就导致了不必要的计算和延迟。
具体代码如下:
from typing import List
from collections import defaultdict
class Solution:
def __init__(self):
# 初始化行程结果和当前路径(始终从JFK开始)
self.result = []
self.path = ["JFK"]
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
# 创建图:这里我们使用字典表示图,key为出发机场,value为可能到达的机场列表
graph = defaultdict(list)
# 先对tickets进行排序,这样我们可以保证较小的目的机场先被考虑
for depart, arrive in sorted(tickets):
graph[depart].append(arrive)
# 调用回溯函数,从JFK开始,总机票数+1是因为路径长度包括起点
self.backtracking(graph, "JFK", len(tickets) + 1)
return self.result
def backtracking(self, graph, current_city, ticket_count):
# 如果当前路径的长度满足机票数+1,那么找到了一个合适的行程
if len(self.path) == ticket_count:
self.result = self.path[:]
return True
# 如果当前城市没有在图中,说明没有下一个目的地可以去
if current_city not in graph:
return False
# 遍历当前机场可以去的所有机场
for i, next_city in enumerate(graph[current_city]):
# 选择:选择一个目的地,更新路径,并从graph中移除这个目的地
self.path.append(next_city)
graph[current_city].pop(i)
# 进行下一步搜索
if self.backtracking(graph, next_city, ticket_count):
return True
# 撤销选择:如果当前选择的目的地没有得到有效的行程,我们撤销选择,恢复graph和路径
self.path.pop()
graph[current_city].insert(i, next_city)
# 如果当前机场的所有可能目的地都没有得到有效的行程,返回False
return False
Leetcode 51 N 皇后
题目描述
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。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
解法
这道题目的难点在于如何去回溯遍历一个二维的棋盘,因为之前我们做过的组合、子集、排列都是一维的序列。套用之前的回溯模板,我们可以让for循环代表棋盘的列,而每层回溯则进入下一层。当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回。
n皇后的回溯部分代码并不难,主要是判断放置位置是否有效比较繁琐,造成心理上以为这道题目很难,实际上,抽离出这部分后,代码还是比较简单。完整代码如下:
class Solution:
def __init__(self):
self.result = [] # 三维数组
def is_valid(self, row: int, col: int, chessboard: List[str]) -> bool:
# 检查列
for i in range(row):
if chessboard[i][col] == 'Q':
return False # 当前列已经存在皇后,不合法
# 检查 45 度角是否有皇后
i, j = row - 1, col - 1
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 # 当前位置合法
def solveNQueens(self, n: int) -> List[List[str]]:
chessboard = [["."] * n for i in range(n)]
self.back_tracking(chessboard, n, 0)
return self.result
def back_tracking(self, chessboard, n, row):
# chessboard为一个二维数组,表示棋盘
# n代表棋盘大小
# row代表当前遍历到了棋盘的哪一层
if row == n:
# 下面的一行代码是错误的,应该需要深拷贝
# self.result.append(chessboard[:])
self.result.append(["".join(row) for row in chessboard])
return
for col in range(0, n):
if self.is_valid(row, col, chessboard):
chessboard[row][col] = 'Q'
self.back_tracking(chessboard, n, row+1)
chessboard[row][col] = "."
Leetcode 37 解数独
题目描述
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9在每一行只能出现一次。 - 数字
1-9在每一列只能出现一次。 - 数字
1-9在每一个以粗实线分隔的3x3宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。
示例 1:
输入: board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出: [["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释: 输入的数独如上图所示,唯一有效的解决方案如下所示:
提示:
board.length == 9board[i].length == 9board[i][j]是一位数字或者'.'- 题目数据 保证 输入数独仅有一个解
解法
使用回溯法解决,还是使用两层循环来遍历整个矩阵,完整代码如下:
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
self.back_tracking(board)
def back_tracking(self, board) -> bool:
"""
使用回溯法解决数独
"""
for row in range(len(board)):
for col in range(len(board[0])):
# 当找到一个空格时,尝试填入1-9的数字
if board[row][col] == ".":
for num in range(1, 10): # 注意这里应该是从1到9
if self.is_valid(row, col, num, board):
board[row][col] = str(num) # 转换为字符串填充
if self.back_tracking(board):
return True # 找到一个有效解决方案
board[row][col] = '.' # 回溯
return False # 如果1-9都试过了,还是不行,就返回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