332. 重新安排行程
- 思路
- example
- 从 JFK(肯尼迪国际机场)出发
- 按字典排序返回最小的行程组合
- 假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
- 有可能某些机场需要访问多次。
有重不可复选?
- 深搜的时候可能遇到回路(从而使得有些机票未访问到),这个时候就需要回溯了。
- 两个难点:
- 在碰到回路(死循环)时,需要回溯。
- 建立字典 map[起始机场] = list(终点机场 按字典顺序排序) 有向图 (邻接表)
- 方便使用”字典顺序“
- 使用pop(0)和append来增删map[]里的终点机场。
- 注意初始path = ['JFK']
- 复杂度. 时间:O(?), 空间: O(?)
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
def backtrack(ticket_dict, start):
if len(path) == len(tickets)+1:
return True
for _ in ticket_dict[start]:
end = ticket_dict[start].pop(0)
path.append(end)
if backtrack(ticket_dict, end):
return True
path.pop()
ticket_dict[start].append(end) # 重新加到末尾
return False
path = ['JFK']
ticket_dict = collections.defaultdict(list)
for ticket in tickets:
ticket_dict[ticket[0]].append(ticket[1])
for start in ticket_dict:
ticket_dict[start].sort()
backtrack(ticket_dict, 'JFK')
return path
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
def backtrack(start):
if len(path) == n+1:
return True
for _ in table[start]:
end = table[start].pop(0)
path.append(end)
if backtrack(end):
return True
path.pop()
table[start].append(end)
return False
table = collections.defaultdict(list)
n = len(tickets)
for i in range(n):
table[tickets[i][0]].append(tickets[i][1])
for key in table:
table[key].sort()
path = ['JFK']
backtrack('JFK')
return path
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
def backtrack(start_pos):
if len(path) == n+1:
return True
for _ in range(len(record[start_pos])): # !!!
end_pos = record[start_pos].pop(0)
path.append(end_pos)
if backtrack(end_pos):
return True
path.pop()
record[start_pos].append(end_pos)
return False
record = collections.defaultdict(list)
n = len(tickets)
for i in range(n):
record[tickets[i][0]].append(tickets[i][1])
for key, val in record.items():
record[key].sort()
path = ['JFK']
backtrack('JFK')
return path
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
def backtrack(start):
print(start)
if len(path) == n+1:
return True
#for end in record[start]: # This is wrong!
for _ in range(len(record[start])): # !!!
end = record[start].pop(0)
path.append(end)
if backtrack(end):
return True
path.pop()
record[start].append(end)
return False
n = len(tickets)
record = collections.defaultdict(list)
for i in range(n):
record[tickets[i][0]].append(tickets[i][1])
for key, val in record.items():
record[key].sort()
path = ['JFK']
backtrack('JFK')
return path
51. N 皇后
- 思路
- example
- 返回所有不同的 n 皇后问题 的解决方案: 回溯(深搜dfs)
- 深度:行数 (深搜)
- 宽度:列数
- 每一行选择可行列(三个方向:列,左上角,右上角),建立3个数组:
- col[i]: 第i列是否已使用, col size: n * 1
- 右上角方向 topright[k]: (i,j)-line with i + j = k, k = 0,1,..., 2n-2; topright size: 2n-1
- 左上角方向 topleft[k]: (i,j)-line with i - j + n - 1 = k, k = 0, 1, ..., 2n-2, topleft size: 2n-1 - 为方便,用grid(n * n)来记录棋盘状态。注意字符串的处理。
- 复杂度. 时间:O(?), 空间: O(?)
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def backtrack(i):
if i == n:
res.append([''.join(row) for row in grid])
return
for j in range(n):
if column[j] or top_right[i+j] or top_left[i-j+n-1]:
continue
grid[i][j] = 'Q'
column[j] = True
top_right[i+j] = True
top_left[i-j+n-1] = True
backtrack(i+1)
top_left[i-j+n-1] = False
top_right[i+j] = False
column[j] = False
grid[i][j] = '.'
res = []
grid = [['.' for _ in range(n)] for _ in range(n)]
column = [False for _ in range(n)]
top_right = [False for _ in range(2*n)]
top_left = [False for _ in range(2*n)]
backtrack(0)
return res
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def backtrack(i):
if i == n:
res.append([''.join(grid[i]) for i in range(n)])
return
for j in range(n):
if col[j] or diag1[i-j+n-1] or diag2[i+j]:
continue
grid[i][j] = 'Q'
col[j] = True
diag1[i-j+n-1] = True
diag2[i+j] = True
backtrack(i+1)
diag2[i+j] = False
diag1[i-j+n-1] = False
col[j] = False
grid[i][j] = '.'
res = []
col = [False for _ in range(n)]
diag1 = [False for _ in range(2*n-1)] # i-j+n-1
diag2 = [False for _ in range(2*n-1)] # i+j
grid = [['.' for _ in range(n)] for _ in range(n)]
backtrack(0)
return res
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def backtrack(i):
if i == n:
res.append([''.join(board[x]) for x in range(n)])
return
for j in range(n):
idx1, idx2 = i+j, j-i+(n-1)
if col[j] or diag1[idx1] or diag2[idx2]:
continue
col[j] = True
diag1[idx1] = True
diag2[idx2] = True
board[i][j] = 'Q'
backtrack(i+1)
board[i][j] = '.'
diag2[idx2] = False
diag1[idx1] = False
col[j] = False
return
res = []
board = [['.' for _ in range(n)] for _ in range(n)]
col = [False for _ in range(n)]
diag1 = [False for _ in range(2*n-1)]
diag2 = [False for _ in range(2*n-1)]
backtrack(0)
return res
37. 解数独
- 思路
- example
- 题目数据 保证 输入数独仅有一个解
- 深搜,dfs, 回溯
- 先确定搜索顺序,先行后列
- 可以把board中的空白部分位置(i,j)存入empty数组(一维)进行深搜
- 对搜索的每一个位置(i,j),模向遍历1-9,利用hash (set,三个限制:行,列,块)存储已使用数字。
- 注意别忘了对这三个hash初始化(建立empty数组的同时更新)
- 复杂度. 时间:O(?), 空间: O(?)
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def backtrack(index):
if index == len(empty):
return True
i, j = empty[index]
for num in range(1, 10):
if num in rows[i] or num in columns[j] or num in blocks[i//3][j//3]:
continue
board[i][j] = str(num)
rows[i].add(num)
columns[j].add(num)
blocks[i//3][j//3].add(num)
if backtrack(index+1):
return True
blocks[i//3][j//3].remove(num)
columns[j].remove(num)
rows[i].remove(num)
board[i][j] = '.'
return False
rows = [set() for _ in range(9)] # 行已用数字
columns = [set() for _ in range(9)] # 列已用数字
blocks = [[set() for _ in range(3)] for _ in range(3)] # 9 blocks
empty = []
for i in range(9):
for j in range(9):
if board[i][j] == ".":
empty.append((i,j))
else:
rows[i].add(int(board[i][j]))
columns[j].add(int(board[i][j]))
blocks[i//3][j//3].add(int(board[i][j]))
backtrack(0)
# modify in-place, no return
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def backtrack(idx):
if idx == len(empty):
return True
i, j = empty[idx]
for num in range(1, 10):
if num in row[i] or num in col[j] or num in box[i//3][j//3]:
continue
board[i][j] = str(num)
row[i].add(num)
col[j].add(num)
box[i//3][j//3].add(num)
if backtrack(idx+1):
return True
box[i//3][j//3].remove(num)
col[j].remove(num)
row[i].remove(num)
board[i][j] = '.'
return False
row = [set() for _ in range(9)]
col = [set() for _ in range(9)]
box = [[set() for _ in range(3)] for _ in range(3)]
empty = []
for i in range(9):
for j in range(9):
if board[i][j] == '.':
empty.append((i,j))
else:
row[i].add(int(board[i][j]))
col[j].add(int(board[i][j]))
box[i//3][j//3].add(int(board[i][j]))
backtrack(0)
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def backtrack(idx):
if idx == len(filling):
return True # !!!
i, j = filling[idx][0], filling[idx][1]
for num in range(1, 10):
if str(num) in row[i] or str(num) in col[j] or str(num) in box[i//3][j//3]:
continue
row[i].add(str(num))
col[j].add(str(num))
box[i//3][j//3].add(str(num))
board[i][j] = str(num)
if backtrack(idx+1):
return True # !!!
board[i][j] = '.'
box[i//3][j//3].remove(str(num))
col[j].remove(str(num))
row[i].remove(str(num))
return False # !!!
row = [set() for _ in range(9)]
col = [set() for _ in range(9)]
box = [[set() for _ in range(3)] for _ in range(3)]
filling = list()
for i in range(9):
for j in range(9):
if board[i][j] != '.':
row[i].add(board[i][j])
col[j].add(board[i][j])
box[i//3][j//3].add(board[i][j])
else:
filling.append((i, j))
backtrack(0)
- 注意下面的区别
rows = [set() for _ in range(9)]
rows2 = [set()] * 9