491.递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
- 输入: [4, 6, 7, 7]
- 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
- 给定数组的长度不会超过15。
- 数组中的整数范围是 [-100,100]。
- 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
思路:
不出意外完美踩进所说的陷阱之中,本题不能用子集的思路去解。首先不能改变原数组的序列,其次去重的方式并不是将紧密排列在一起的元素进行去重,而是在一个循环中已经出现过的元素都不能再取,因为相同的元素可能不紧密排列在一起,中间可能会隔着一个不符合条件的小元素。至此,其他思路就和求子集区别不大了,需要注意的是只有符合条件的元素才进行回溯调用,所以调用了就说明进入调用的元素是符合条件的,此时应该直接将此时的结果压入结果数组,同时不进行返回,这样就实现了中途符合条件的所有数组都能被压入。
代码实现如下:
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
self.result = []
self.backtracking(nums, 0, [])
return self.result
def backtracking(self, nums:List[int], start:int, path:List):
if len(path)>=2:
self.result.append((path[:]))
dict = {}
for i in range(start, len(nums)):
#if (dict.get(nums[i], 0) == 0 and (path and nums[i]>=path[-1])) or not path:
if dict.get(nums[i], 0) == 0 and (not path or nums[i]>=path[-1]):
dict[nums[i]] = 1
path.append(nums[i])
self.backtracking(nums, i+1, path)
path.pop()
规范代码:
回溯 利用set去重
class Solution:
def findSubsequences(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
if len(path) > 1:
result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集
# 注意这里不要加return,要取树上的节点
uset = set() # 使用集合对本层元素进行去重
for i in range(startIndex, len(nums)):
if (path and nums[i] < path[-1]) or nums[i] in uset:
continue
uset.add(nums[i]) # 记录这个元素在本层用过了,本层后面不能再用了
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
回溯 利用哈希表去重
class Solution:
def findSubsequences(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
if len(path) > 1:
result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集
used = [0] * 201 # 使用数组来进行去重操作,题目说数值范围[-100, 100]
for i in range(startIndex, len(nums)):
if (path and nums[i] < path[-1]) or used[nums[i] + 100] == 1:
continue # 如果当前元素小于上一个元素,或者已经使用过当前元素,则跳过当前元素
used[nums[i] + 100] = 1 # 标记当前元素已经使用过
path.append(nums[i]) # 将当前元素加入当前递增子序列
self.backtracking(nums, i + 1, path, result)
path.pop()
46.全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
思路:
全局定义一个记录元素是否被使用的集合used,每当当前path数组记录某元素后,就将元素压入set中,所以在每一次遍历压入之前应该先检查元素是否在used集合中,如果存在说明当前已被取用,不应该对本元素进行回溯调用,而是调用下一个元素。当遍历的path长度与nums一致时,即所有元素被调用,此时压入结果数组中。
代码实现如下:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.uset = set()
self.result = []
self.backtracking(nums, 0, [])
return self.result
def backtracking(self, nums:List[int], pos:int, path:List):
if pos >= len(nums):
self.result.append(path[:])
for i in range(0, len(nums)):
if nums[i] not in self.uset:
self.uset.add(nums[i])
path.append(nums[i])
self.backtracking(nums, pos+1, path)
path.pop()
self.uset.remove(nums[i])
规范代码:
回溯 使用used
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]]
提示:
- 1 <= nums.length <= 8
- -10 <= nums[i] <= 10
思路:
与上一题全排列不同的是,本题将会出现重复元素,所以在每一层for循环中,需要有一个cur_uset来记录本层for循环是否已经取用过某一种元素,确保不会在相同的位置取到相同的元素。而关于一个结果数组中,确保元素出现次数正确方面的去重,应该在全局下定义一个记录每种元素出现过的次数,当元素还有余量时可以继续取用。确保这两方面的条件之后,和上一题一样的思路进行回溯即可。
代码实现如下:
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
self.result = []
#self.uset = set()
self.count = defaultdict(int)
for num in nums:
self.count[num] += 1
self.backtracking(nums, 0, [])
return self.result
def backtracking(self, nums: List[int], pos: int, path: List):
if pos >= len(nums):
self.result.append(path[:])
cur_uset = set()
for i in range(0, len(nums)):
if nums[i] in cur_uset:
continue
#if nums[i] not in self.uset:
if self.count[nums[i]] > 0:
#self.uset.add(nums[i])
self.count[nums[i]] -= 1
cur_uset.add(nums[i])
path.append(nums[i])
self.backtracking(nums, pos + 1, path)
path.pop()
#self.uset.remove(nums[i])
self.count[nums[i]] += 1
规范代码:
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()
used[i] = False
332.重新安排行程
思路:没想出来,记录思路。字典记载:key为出发点,value为可到达的机场位置(同时以value为依据进行排序)。从出发点开始遍历,每次都以出发点对应的可到达位置的首个位置进行递归调用并pop,路径长度刚好等于机票数+1,说明得到答案进行返回。
规范代码:
回溯 使用字典
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
回溯 使用字典 逆序
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) # 将当前机场添加到行程路径中
51. N皇后
记录思路:每个棋盘的每一行就是我们的一层for循环,依次遍历放置Queen,当遍历到最后一行并且可以放置Queen的时候就到了终止条件,记录数据。在每次遍历的时候,需要检查位置是否有效,所以定义一个函数来进行检查所在位置的上方、左上方、右上方是否存在Queen,由于是从左向右遍历寻找放置Queen的位置,所以不需要检索行。
规范代码:
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. 解数独
记录思路:重点在于二维递归,即对9*9的方格,每一个[i, j]都要进行一次遍历。本题由于限定了一定是9*9的方格,所以在循环完这(9*9)*9次递归回溯后,一定会退出循环,所以可以不需要终止条件,如果跳出了这个循环说明没有正确答案,可以直接返回False。每一个格子的递归逻辑是,如果有数字则跳过。为空则在每一个格子都遍历1~9来进行回溯调用,确认放置元素之前要检查该数是否有效,依次排查行、列、九宫格内是否满足条件(该数唯一),如果有效则对下一个格子进行1~9的遍历调用。
规范代码:
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
self.backtracking(board)
def backtracking(self, board: List[List[str]]) -> bool:
# 若有解,返回True;若无解,返回False
for i in range(len(board)): # 遍历行
for j in range(len(board[0])): # 遍历列
# 若空格内已有数字,跳过
if board[i][j] != '.': continue
for k in range(1, 10):
if self.is_valid(i, j, k, board):
board[i][j] = str(k)
if self.backtracking(board): return True
board[i][j] = '.'
# 若数字1-9都不能成功填入空格,返回False无解
return False
return True # 有解
def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:
# 判断同一行是否冲突
for i in range(9):
if board[row][i] == str(val):
return False
# 判断同一列是否冲突
for j in range(9):
if board[j][col] == str(val):
return False
# 判断同一九宫格是否有冲突
start_row = (row // 3) * 3
start_col = (col // 3) * 3
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == str(val):
return False
return True