* 491.递增子序列
* 46.全排列
* 47.全排列 II
332.重新安排行程
51. N皇后
37. 解数独
总结
491.递增子序列
注意使用set来判断这一层是否有用过相同的数字,因为这题的数不是order的,所以不能和前者相对比
在python中的set使用add来增加
class Solution: def findSubsequences(self, nums: List[int]) -> List[List[int]]: result = [] ans_now = [] self.combination(nums, result, ans_now, 0) return result def combination(self, nums, result, ans_now, index): if (len(ans_now) >= 2): result.append(ans_now[:]) used = set() for i in range(index, len(nums)): if(index > 0 and nums[index-1] > nums[i]): continue if(nums[i] in used): continue used.add(nums[i]) ans_now.append(nums[i]) self.combination(nums, result, ans_now, i+1) ans_now.pop() return
46.全排列
class Solution: # def backtrack(self, use_list, num_left, now_list): # if num_left == 0: # self.answer.append(now_list) # return # for i in range(self.n): # if use_list[i]: # use_list[i] = False # now_list.append(self.nums[i]) # self.backtrack(use_list, num_left-1, now_list[:]) # now_list.pop() # use_list[i] = True # def permute(self, nums: List[int]) -> List[List[int]]: # self.nums = nums # self.answer = [] # self.n = len(nums) # use = [True for _ in range(self.n)] # self.backtrack(use, self.n, []) # return self.answer def permute(self, nums: List[int]) -> List[List[int]]: result = [] now_ans = [] use = [True for _ in range(len(nums))] self.backtrack(use, result, now_ans, nums) return result def backtrack(self, use, result, now_ans, nums): if len(now_ans) == len(nums): result.append(now_ans[:]) return for i in range(len(nums)): if (use[i]): now_ans.append(nums[i]) use[i] = False self.backtrack(use, result, now_ans, nums) use[i] = True now_ans.pop() return
47.全排列 II
遇到重复问题
从树的每一层解决源头性问题
使用used的表格
class Solution: def permuteUnique(self, nums: List[int]) -> List[List[int]]: ans = [] result = [] use = [False for _ in nums] self.backtracking(nums, ans, result, use) return result def backtracking(self, nums, ans, result, use): if len(ans) == len(nums): result.append(ans[:]) return used = set() for i in range(len(nums)): if ((nums[i] in used) or use[i] ): continue used.add(nums[i]) use[i] = True ans.append(nums[i]) self.backtracking(nums, ans, result, use) use[i] = False ans.pop()
332.重新安排行程
依旧采用树的思想
想明白什么时候应该回溯
什么时候应该直接通过
可以考虑说返回bool类型进行判断
注意可以直接用sort进行排序
class Solution: def findItinerary(self, tickets: List[List[str]]) -> List[str]: tickets.sort() used = [True for _ in range(len(tickets))] result = ['JFK'] self.backtracking(tickets, used, result, 'JFK',0) return result def backtracking(self, tickets, used, result, now, num): if(num == len(tickets)): return True for i in range(len(tickets)): temp = tickets[i] if used[i] and temp[0] == now: used[i] = False result.append(temp[1]) if(self.backtracking(tickets, used, result, temp[1], num+1)): return True result.pop() used[i] = True return False
from collections import defaultdict class Solution: def findItinerary(self, tickets): targets = defaultdict(list) # 创建默认字典,用于存储机场映射关系 for ticket in tickets: targets[ticket[0]].append(ticket[1]) # 将机票输入到字典中 for key in targets: targets[key].sort(reverse=True) # 对到达机场列表进行字母逆序排序 result = [] self.backtracking("JFK", targets, result) # 调用回溯函数开始搜索路径 return result[::-1] # 返回逆序的行程路径 def backtracking(self, airport, targets, result): while targets[airport]: # 当机场还有可到达的机场时 next_airport = targets[airport].pop() # 弹出下一个机场 self.backtracking(next_airport, targets, result) # 递归调用回溯函数进行深度优先搜索 result.append(airport) # 将当前机场添加到行程路径中
可以使用字典的倒叙进行解决这题, 并且可以使用pop和append进行机票是否使用的
51. N皇后
典中典的题目 哈哈哈
一次过
class Solution: # def checking(self, now, ans): # n = len(ans) # if n == 0: # return True # if now in ans: # return False # for i in range(n): # if ans[i] == (now-n+i) or ans[i] == (now+n-i): # return False # return True # def backtracking(self, ans, used): # #print(ans) # if len(ans) == self.n: # self.solution.append(ans) # return # for i in range(self.n): # if self.checking(i, ans): # used[i] = True # ans.append(i) # self.backtracking(ans[:], used[:]) # used[i] = False # ans.pop() # def solveNQueens(self, n: int) -> List[List[str]]: # self.solution = [] # self.n = n # used = [False for _ in range(n)] # self.backtracking([], used) # answer = [] # for i in self.solution: # single_ans = [] # for j in i: # line = "."*(j) + 'Q' + '.'*(n-j-1) # single_ans.append(line) # answer.append(single_ans) # return answer def solveNQueens(self, n: int) -> List[List[str]]: result = [] ans = [] self.backtracking(result, ans, n) results = [] for i in range(len(result)): temp = [] for j in result[i]: s = "."*(j)+'Q'+'.'*(n-1-j) temp.append(s) results.append(temp[:]) return results def backtracking(self, result, ans, n): if len(ans) == n: result.append(ans[:]) return for i in range(n): if self.checking(ans, i, n): ans.append(i) self.backtracking(result, ans, n) ans.pop() return def checking(self, ans, k, n): if k in ans: return False l = len(ans) for i in range(l): left = k-i-1 if left >= 0 and ans[l-i-1] == left: return False right = k+i+1 if right < n and ans[l-i-1] == right: return False return True
37. 解数独
class Solution: def solveSudoku(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ self.backtracking(0,0,board) def isValid(self, row, col, ch, board): row, col = int(row), int(col) for i in range(9): if board[i][col] == ch: return False if board[row][i] == ch: return False if board[3*(row//3)+i//3][3*(col//3)+i%3] == ch: return False return True def backtracking(self, row, col, board): if row == 9: return True if col == 9: return self.backtracking(row+1, 0, board) if board[row][col] == '.': for i in range(1, 10): if self.isValid(row, col, str(i), board): board[row][col] = str(i) if self.backtracking(row, col+1, board): return True board[row][col] = '.' return False else: return self.backtracking(row, col+1, board)
总结
子集问题分析:一定要排序后去重
- 时间复杂度:$O(n × 2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$,构造每一组子集都需要填进数组,又有需要$O(n)$,最终时间复杂度:$O(n × 2^n)$。
- 空间复杂度:$O(n)$,递归深度为n,所以系统栈所用空间为$O(n)$,每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为$O(n)$。
排列问题分析:
- 时间复杂度:$O(n!)$,这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。每个叶子节点都会有一个构造全排列填进数组的操作(对应的代码:
result.push_back(path)
),该操作的复杂度为$O(n)$。所以,最终时间复杂度为:n * n!,简化为$O(n!)$。 - 空间复杂度:$O(n)$,和子集问题同理。
组合问题分析:
- 时间复杂度:$O(n × 2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
- 空间复杂度:$O(n)$,和子集问题同理。