332.重新安排行程
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
提示:
- 如果存在多种有效的行程,请你按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前
- 所有的机场都用三个大写字母表示(机场代码)。
- 假定所有机票至少存在一种合理的行程。
- 所有的机票必须都用一次 且 只能用一次。
示例 1:
- 输入:[["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]
- 输出:["JFK", "MUC", "LHR", "SFO", "SJC"]
示例 2:
- 输入:[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
- 输出:["JFK","ATL","JFK","SFO","ATL","SFO"]
- 解释:另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。
解题思路:
1. 把机票看作箭头:
首先,你可以把每张机票看作一个箭头,箭头的尾部是出发机场,箭头的头部是到达机场。
2. 构建机场的连接图:
我们可以把所有机票放在一张图上,每一个机场是一个点,每一张机票就是从一个点指向另一个点的箭头。
3. 从 JFK 开始:
题目说我们的旅行始终从 JFK 开始,所以我们从 JFK 这个点开始,尝试走向其他的点。
4. 选择下一个机场:
每次选择下一个机场时,我们都会选择字母顺序最小的那个,这样保证我们得到的行程是字典序最小的。
5. 递归地探索:
从 JFK 开始,我们会递归地探索每一个可以去的机场,直到我们用完了所有的机票。
6. 回溯并记录行程:
当我们到达某个机场后发现没有办法继续前进(即没有后续机票),或者已经用完了所有机票,我们就把当前机场记录下来,并返回到上一个机场,尝试其他可能的路径。
7. 得到的行程是反的:
由于我们是在回溯时才记录机场,所以得到的行程实际上是反的。例如,我们可能先得到 SJC,然后是 SFO,最后才是 JFK。因此,在返回答案之前,需要将行程反转。
8. 结果就是我们的行程:
最后,按照上述方法,我们可以得到一条经过所有机票的、字母顺序最小的行程。
from typing import List
import heapq
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
# 创建一个映射,来存储从一个地点到其他所有地点的票
adj = {}
for start, end in tickets:
if start not in adj:
adj[start] = []
heapq.heappush(adj[start], end) # 用堆,保证字典序最小的机场优先出队
path = []
def dfs(airport):
# 访问机场的所有邻居机场,直到当前机场没有邻居为止
while airport in adj and adj[airport]:
dfs(heapq.heappop(adj[airport])) # 从堆中取出字典序最小的机场,并从邻接表中移除
path.append(airport) # 添加到路径中
dfs("JFK")
return path[::-1] # 返回逆序的路径
例子:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
- 构建邻接表,将每张票的起点和终点添加到字典中:
adj = { "JFK": ["ATL", "SFO"], "SFO": ["ATL"], "ATL": ["JFK", "SFO"] }
注意,由于我们使用了堆,因此每个机场的列表中,机场是按照字典序排列的。
-
从"JFK"开始进行深度优先搜索。
-
在搜索的过程中,每访问一个机场,就将该机场从邻接表中的当前机场列表中移除,并递归访问新的机场。
-
当一个机场没有邻接的机场时,将其添加到
path
中。 -
搜索结束后,
path
的逆序即为答案。
代码逻辑和Python语法:
- 使用
heapq
模块创建了一个优先队列,确保始终选择字典序最小的机场。 - 在
dfs
函数中,我们递归地访问每个机场的所有邻接机场。 - 使用
heapq.heappop
函数从堆中取出字典序最小的机场,并从邻接表中移除它。 - 当一个机场没有邻接的机场时,将其添加到
path
中。 - 最后,返回
path
的逆序作为答案,因为在深度优先搜索中,我们实际上是在访问完所有邻接机场后才添加当前机场到路径中的。
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"]]
"N 皇后"问题是一个经典的回溯算法问题,其目标是在一个 n×n 的棋盘上放置 n 个皇后,使得它们不互相攻击。皇后可以攻击同一行、同一列和同一对角线上的其他棋子。解决这个问题的思路如下:
-
创建一个空的 n×n 棋盘,用一个二维数组来表示,初始时所有位置都标记为".",表示空位。
-
从第一行开始,逐行放置皇后。对于每一行,从左到右依次尝试将皇后放在不会受到攻击的位置。
-
在放置皇后的过程中,需要检查当前位置是否满足以下条件:
- 同一列没有其他皇后。
- 左上对角线上没有其他皇后。
- 右上对角线上没有其他皇后。
-
如果找到一个位置可以放置皇后,就将该位置标记为"Q",表示皇后的位置,然后进入下一行继续尝试放置下一个皇后。
-
如果无法找到合适的位置放置皇后,回溯到上一行,重新选择其他位置放置上一行的皇后,并继续尝试下一列。
-
当成功放置了 n 个皇后时,即完成了一种解决方案,将当前棋盘的状态保存下来,然后继续尝试下一种解决方案。
-
重复以上步骤,直到找到所有合法的解决方案。
-
最后,返回所有找到的解决方案。
这个思路通过回溯算法实现,不断尝试不同的位置,当发现无法继续放置皇后时,回溯到上一步,直到找到所有可能的解决方案为止。这种方法确保了找到的解决方案满足题目的要求,即皇后不互相攻击。
from typing import List
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
# 初始化一个空棋盘,用二维数组表示
board = [['.' for _ in range(n)] for _ in range(n)]
# 用于存储所有解决方案
solutions = []
def is_valid(row, col):
# 检查同一列是否有皇后
for i in range(row):
if board[i][col] == 'Q':
return False
# 检查左上对角线是否有皇后
i, j = row - 1, col - 1
while i >= 0 and j >= 0:
if board[i][j] == 'Q':
return False
i -= 1
j -= 1
# 检查右上对角线是否有皇后
i, j = row - 1, col + 1
while i >= 0 and j < n:
if board[i][j] == 'Q':
return False
i -= 1
j += 1
return True
def backtrack(row):
if row == n:
# 找到一个解决方案,将棋盘状态添加到结果列表中
solutions.append([''.join(row) for row in board])
return
for col in range(n):
if is_valid(row, col):
# 在当前位置放置皇后
board[row][col] = 'Q'
# 继续下一行的放置
backtrack(row + 1)
# 回溯,重置当前位置
board[row][col] = '.'
# 从第一行开始放置皇后
backtrack(0)
return solutions
37. 解数独
编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。
一个数独。
答案被标成红色。
提示:
- 给定的数独序列只包含数字 1-9 和字符 '.' 。
- 你可以假设给定的数独只有唯一解。
- 给定数独永远是 9x9 形式的。
from typing import List
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
:param board: List[List[str]]
:return: None
"""
self.solve(board)
def solve(self, board: List[List[str]]) -> bool:
for i in range(9):
for j in range(9):
if board[i][j] == ".":
for num in map(str, range(1, 10)):
if self.is_valid(board, i, j, num):
board[i][j] = num
if self.solve(board):
return True
board[i][j] = "."
return False
return True
def is_valid(self, board: List[List[str]], row: int, col: int, num: str) -> bool:
for i in range(9):
if board[i][col] == num or board[row][i] == num:
return False
row_start, col_start = 3 * (row // 3), 3 * (col // 3)
for i in range(row_start, row_start + 3):
for j in range(col_start, col_start + 3):
if board[i][j] == num:
return False
return True