wuyijia

导航

代码随想录算法训练营-回溯算法|491.递增子序列、46. 全排列、47. 全排列 II、332. 重新安排行程、37. 解数独

491. 递增子序列

 不对原数组进行排序,利用set对同层的子集进行去重。

 1 class Solution:
 2     def findSubsequences(self, nums):
 3         result = []
 4         path = []
 5         self.backtracking(nums, 0, path, result)
 6         return result
 7     
 8     def backtracking(self, nums, startIndex, path, result):
 9         if len(path) > 1:
10             result.append(path[:])  # 注意要使用切片将当前路径的副本加入结果集
11             # 注意这里不要加return,要取树上的节点
12         
13         uset = set()  # 使用集合对本层元素进行去重
14         for i in range(startIndex, len(nums)):
15             if (path and nums[i] < path[-1]) or nums[i] in uset:
16                 continue
17             
18             uset.add(nums[i])  # 记录这个元素在本层用过了,本层后面不能再用了
19             path.append(nums[i])
20             self.backtracking(nums, i + 1, path, result)
21             path.pop()

 46. 全排列

 不用像子集和切割问题使用startIndex,但需要一个used数组记录path里面都有哪些元素被使用了。
  • 时间复杂度: O(n!)
  • 空间复杂度: O(n)
 1 class Solution:
 2     def permute(self, nums):
 3         result = [] 
 4         #used数组=[False]*len(nums)
 5         self.backtracking(nums, [], [False] * len(nums), result)
 6         return result
 7 
 8     def backtracking(self, nums, path, used, result):
 9         #停止条件:到达叶子结点:全排列
10         if len(path) == len(nums): 
11             result.append(path[:])
12             return
13         for i in range(len(nums)):#for循环控制树寛
14             if used[i]: #被使用的元素不能再被选择,因为for是从0开始,所以需要一个used数组
15                 continue
16             #处理节点
17             used[i] = True 
18             path.append(nums[i])
19             #递归
20             self.backtracking(nums, path, used, result)
21             #回溯
22             path.pop()
23             used[i] = False

47. 全排列 II

1. 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

2. 与46题的区别在于去重,去重一定要对元素进行排序,才方便通过相邻的节点来判断是否重复使用了。

3. 对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
  • 时间复杂度: O(n! * n)
  • 空间复杂度: O(n)
 1 class Solution:
 2     def permuteUnique(self, nums):
 3         nums.sort()  # 排序
 4         result = []
 5         self.backtracking(nums, [], [False] * len(nums), result)
 6         return result
 7 
 8     def backtracking(self, nums, path, used, result):
 9         if len(path) == len(nums):
10             result.append(path[:])
11             return
12         for i in range(len(nums)):
13             if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]: #树层去重
14                 continue
15             used[i] = True
16             path.append(nums[i])
17             self.backtracking(nums, path, used, result)
18             path.pop()
19             used[i] = False

 332. 重新安排行程

用used数组记录机票是否被使用过,依据题意记得先对机票tickets排序。

 1 class Solution:
 2     def findItinerary(self, tickets: List[List[str]]) -> List[str]:
 3         tickets.sort() # 先排序,这样一旦找到第一个可行路径,一定是字母排序最小的
 4         used = [0] * len(tickets)
 5         path = ['JFK']
 6         results = []
 7         self.backtracking(tickets, used, path, 'JFK', results)
 8         return results[0]
 9     
10     def backtracking(self, tickets, used, path, cur, results): #cur出发点
11         if len(path) == len(tickets) + 1:  # 终止条件:路径长度等于机票数量+1
12             results.append(path[:])  # 将当前路径添加到结果列表
13             return True
14         
15         for i, ticket in enumerate(tickets):  # 遍历机票列表
16             if ticket[0] == cur and used[i] == 0:  # 找到起始机场为cur且未使用过的机票
17                 used[i] = 1  # 标记该机票为已使用
18                 path.append(ticket[1])  # 将到达机场添加到路径中
19                 state = self.backtracking(tickets, used, path, ticket[1], results)  # 递归搜索 #上一个降落点是下一个出发点
20                 path.pop()  # 回溯,移除最后添加的到达机场
21                 used[i] = 0  # 标记该机票为未使用
22                 if state:
23                     return True  # 只要找到一个可行路径就返回,不继续搜索

51. N 皇后

 皇后们的约束条件:
  1. 不能同行
  2. 不能同列
  3. 不能同斜线

N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。

 1 class Solution:
 2     def solveNQueens(self, n: int) -> List[List[str]]:
 3         result = []  # 存储最终结果的二维字符串数组
 4 
 5         chessboard = ['.' * n for _ in range(n)]  # 初始化棋盘['....', '....', '....', '....']
 6         self.backtracking(n, 0, chessboard, result)  # 回溯求解
 7         return [[''.join(row) for row in solution] for solution in result]  # 返回结果集
 8 
 9     def backtracking(self, n: int, row: int, chessboard: List[str], result: List[List[str]]) -> None:
10         if row == n:#遍历完了整个棋盘,是收集结果的时候 ,收集结果的时候就是满足要求的时候
11             result.append(chessboard[:])  # 棋盘填满,将当前解加入结果集
12             return
13 
14         for col in range(n): #遍历一行中的所有列
15             if self.isValid(row, col, chessboard): #判断是否满足N皇后要求
16                 chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:]  # 放置皇后
17                 self.backtracking(n, row + 1, chessboard, result)  # 递归到下一行
18                 chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:]  # 回溯,撤销当前位置的皇后
19 
20     def isValid(self, row: int, col: int, chessboard: List[str]) -> bool:
21         # 检查列
22         for i in range(row):
23             if chessboard[i][col] == 'Q':
24                 return False  # 当前列已经存在皇后,不合法
25 
26         # 检查 45 度角是否有皇后
27         i, j = row - 1, col - 1
28         while i >= 0 and j >= 0:
29             if chessboard[i][j] == 'Q':
30                 return False  # 左上方向已经存在皇后,不合法
31             i -= 1
32             j -= 1
33 
34         # 检查 135 度角是否有皇后
35         i, j = row - 1, col + 1
36         while i >= 0 and j < len(chessboard):
37             if chessboard[i][j] == 'Q':
38                 return False  # 右上方向已经存在皇后,不合法
39             i -= 1
40             j += 1
41 
42         return True  # 当前位置合法

 37. 解数独

1. 与以往的不同在于这道题使用二维递归。
2. 解数独的树形结构要比N皇后更宽更深。
3. 解数独找到一个符合的条件(就在树的叶子节点上)立刻就返回,相当于找从根节点到叶子节点一条唯一路径,所以需要使用bool返回值。
4. 一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
 1 class Solution:
 2     def solveSudoku(self, board: List[List[str]]) -> None:
 3         """
 4         Do not return anything, modify board in-place instead.
 5         """
 6         self.backtracking(board)
 7 
 8     def backtracking(self, board: List[List[str]]) -> bool:
 9         # 若有解,返回True;若无解,返回False
10         for i in range(len(board)): # 遍历行
11             for j in range(len(board[0])):  # 遍历列
12                 # 若空格内已有数字,跳过
13                 if board[i][j] != '.': continue
14                 for k in range(1, 10):
15                     if self.is_valid(i, j, k, board):
16                         board[i][j] = str(k)
17                         if self.backtracking(board): return True
18                         board[i][j] = '.'
19                 # 若数字1-9都不能成功填入空格,返回False无解
20                 return False
21         return True # 有解
22 
23     def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:
24         # 判断同一行是否冲突
25         for i in range(9):
26             if board[row][i] == str(val):
27                 return False
28         # 判断同一列是否冲突
29         for j in range(9):
30             if board[j][col] == str(val):
31                 return False
32         # 判断同一九宫格是否有冲突
33         start_row = (row // 3) * 3
34         start_col = (col // 3) * 3
35         for i in range(start_row, start_row + 3):
36             for j in range(start_col, start_col + 3):
37                 if board[i][j] == str(val):
38                     return False
39         return True

 

 

posted on 2023-09-10 13:19  小吴要努力  阅读(5)  评论(0编辑  收藏  举报