代码随想录算法训练营第三十天|491. 非递减子序列,46. 全排列,47. 全排列 II,332. 重新安排行程,51. N 皇后,37. 解数独
491. 非递减子序列,46. 全排列,47. 全排列 II,332. 重新安排行程,51. N 皇后,37. 解数独
- 491. 非递减子序列
- 46. 全排列
- 47. 全排列 II
- 332. 重新安排行程
- 51. N 皇后
- 37. 解数独
491. 非递减子序列
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
示例 2:
输入:nums = [4,4,3,2,1]
输出:[[4,4]]
注意这道题数组不能排序,会破坏数组结构。有重复元素在同一图层的情况,则同一父节点下的同层上使用过的元素就不能再使用了,参考代码使用数组来进行去重操,记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层。
tips:是否增加return是判断是否输出到叶节点。回溯题目在做时候,先画树便于理解,注意同一图层和同一分支元素是哪里生效的,下面一题增加了同一分支的判断。
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
result = []
self.backtracking(nums,0,[],result)
return result
def backtracking(self,s,index,path,result):
if len(path)>1:
result.append(path[:])
used = [0] * 201 # 使用数组来进行去重操作,题目说数值范围[-100, 100]
for i in range(index,len(s)):
if (path and s[i] < path[-1]) or used[s[i] + 100] == 1:
continue # 如果当前元素小于上一个元素,或者已经使用过当前元素,则跳过当前元素
used[s[i] + 100] = 1 # 标记当前元素已经使用过
path.append(s[i]) # 将当前元素加入当前递增子序列
self.backtracking(s, i + 1, path, result)
path.pop()
46. 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
全排列,每次回溯从0开始,增加一个判断当前节点是否遍历过的判断,可使用字典或索引列表,下面是使用字典的例子,当前遍历值在输出数字
p
a
t
h
path
path中则跳过。最终输出结果是
p
a
t
h
path
path数组长度等于原数组长度。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
self.backtracking(nums,0,[],result)
return result
def backtracking(self,s,index,path,result):
if len(path)==len(s):
result.append(path[:])
for i in range(index,len(s)):
if s[i] in path:
continue
path.append(s[i])
self.backtracking(s, 0, path, result)
path.pop()
参考代码用use数组记录是否使用。
class Solution:
def permute(self, nums):
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
47. 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
增加一个数组记录索引,同一图层重复元素判断用判断回溯重复数字的used数组。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
self.backtracking(nums,[],[],result)
return result
def backtracking(self,s,path,root,result):
if len(path)==len(s):
result.append(path[:])
return result
used = [0]* 20
for i in range(len(s)):
if i in root or used[s[i]+10]==1:
continue
path.append(s[i])
root.append(i)
used[s[i]+10]=1
self.backtracking(s, path, root,result)
path.pop()
root.pop()
参考代码,只用一个数组进行回溯,注意跳过循环的情况。
class Solution:
def permuteUnique(self, nums):
nums.sort() # 排序
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
接下来是回溯题目几道比较难的应用,先学习一下思路。
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”] ,但是它字典排序更大更靠后。
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
self.adj = {}
# sort by the destination alphabetically
# 根据航班每一站的重点字母顺序排序
tickets.sort(key=lambda x:x[1])
# get all possible connection for each destination
# 罗列每一站的下一个可选项
for u,v in tickets:
if u in self.adj: self.adj[u].append(v)
else: self.adj[u] = [v]
# 从JFK出发
self.result = []
self.dfs("JFK") # start with JFK
return self.result[::-1] # reverse to get the result
def dfs(self, s):
# if depart city has flight and the flight can go to another city
while s in self.adj and len(self.adj[s]) > 0:
# 找到s能到哪里,选能到的第一个机场
v = self.adj[s][0] # we go to the 1 choice of the city
# 在之后的可选项机场中去掉这个机场
self.adj[s].pop(0) # get rid of this choice since we used it
# 从当前的新出发点开始
self.dfs(v) # we start from the new airport
self.result.append(s) # after append, it will back track to last node, thus the result list is in reversed order
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皇后问题,主要是isValid函数。判断当前位置插入是否有效,终止条件是遍历到最后一行或一列,此时判断已经插入的棋子数量,决定输出。
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
chessboard = ['.' * n for _ in range(n)] # 初始化棋盘
result = []
path = []
self.dfs(n, result, path, 0, 0, chessboard)
return result
def dfs(self, n, result, path, istart, j, chessboard):
if istart == n or j == n:
if len(path) == n:
result.append(chessboard[:])
for i in range(istart, n):
for j in range(n):
if self.isValid(n, i, j, path):
chessboard[i] = chessboard[i][:j] + 'Q' + chessboard[i][j + 1:]
path.append([i, j])
self.dfs(n, result, path, i + 1, j, chessboard)
chessboard[i] = chessboard[i][:j] + '.' + chessboard[i][j + 1:]
path.pop()
def isValid(self, n, i, j, path):
for t in path:
if t[0] == i or t[1] == j:
return False
ii, jj = i - 1, j - 1
# 检查 45 度角是否有皇后
while ii >= 0 and jj >= 0:
if [ii, jj] in path:
return False # 左上方向已经存在皇后,不合法
ii -= 1
jj -= 1
# 检查 135 度角是否有皇后
ii, jj = i - 1, j + 1
while ii >= 0 and jj < n:
if [ii, jj] in path:
return False # 右上方向已经存在皇后,不合法
ii -= 1
jj += 1
return True
附参考代码
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
result = [] # 存储最终结果的二维字符串数组
chessboard = ['.' * n for _ in range(n)] # 初始化棋盘
self.backtracking(n, 0, chessboard, result) # 回溯求解
return [[''.join(row) for row in solution] for solution in result] # 返回结果集
def backtracking(self, n: int, row: int, chessboard: List[str], result: List[List[str]]) -> None:
if row == n:
result.append(chessboard[:]) # 棋盘填满,将当前解加入结果集
return
for col in range(n):
if self.isValid(row, col, chessboard):
chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:] # 放置皇后
self.backtracking(n, row + 1, chessboard, result) # 递归到下一行
chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:] # 回溯,撤销当前位置的皇后
def isValid(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 # 当前位置合法
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”]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示: