目录
- 组合总和
- 组合总和 II
- 全排列
- 全排列 II
- 括号生成
- 分割回文串
- N 皇后
回溯法:深度优先搜索
回溯法思路的简单描述:把问题的解空间转化成了图或树结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类似于:
1.图的深度优先搜搜
2.二叉树的后序遍历
回溯法非常适合由多个步骤组成的问题,并且每个步骤都有多个选项,当我们在每一步选择了其中一个选项时,就进入下一步,然后又面临新的选项。就这样重复的选择着,直到达到最后的状态
1.路径:也就是已经做出的选择。
2.选择的列表:也就是你当前可以做的选择。
3.结束条件:也就是达到决策树底层,无法再做选择的条件
回溯法框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
核心思想:就是for 循环里面的递归,在递归调用之前,做选择,在递归调用之后,撤回选择。
- 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
解题思路:
题目分析:本题采用递归较容易解答。首先,将数组内的元素由小到大排序,然后不断地进行试探,直到和为target。若当前和小于target,则继续往里面加元素;若当前和等于target,就可以将结果保留下来;若当前和已经大于target,那就可以排除掉这种情况,回溯到上一层,寻找其他可能的组合。本质上我觉得可以归纳为深度搜索。
代码如下:
class Solution:
def combinationSum(self, candidates, target):
candidates.sort()
n = len(candidates)
res = []
def backtrack(i, tmp_sum, tmp):
if tmp_sum == target:
res.append(tmp)
return # 满足条件,返回上一级,继续深度遍历寻找可能解
for j in range(i, n):
if tmp_sum + candidates[j] > target: # 在当前位置上,停止往下深度遍历搜索
break
backtrack(j,tmp_sum + candidates[j],tmp+[candidates[j]])
backtrack(0, 0, [])
return res
- 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
if not candidates:
return []
candidates.sort()
n = len(candidates)
res = []
def backtrack(i, tmp_sum, tmp_list):
if tmp_sum == target:
res.append(tmp_list)
return
for j in range(i, n):
if tmp_sum + candidates[j] > target :
break
if j > i and candidates[j] == candidates[j-1]: # 解集不能包含重复的组合。
continue
# candidates 中的每个数字在每个组合中只能使用 一次
backtrack(j + 1, tmp_sum + candidates[j], tmp_list + [candidates[j]])
backtrack(0, 0, [])
return res
- 全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
解题思路: 使用一个和nums长度一样的数列,记录是否使用到这个元素,使用到以后把这个数列对应的位置改为True
解题代码:
回溯思想:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
mark = [False] * len(nums)
outcome =[]
curr = []
def dfs():
if sum(mark) ==len(nums):
outcome.append(curr[:]) #变量curr所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表。
for i in range(len(nums)):
if mark[i] :
continue
curr.append(nums[i])
mark[i] = True
dfs()
mark[i] = False
curr.pop()
dfs()
return outcome
递归思想:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(nums, tmp):
if not nums:
res.append(tmp)
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
backtrack(nums, [])
return res
- 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
剪枝条件
- 用过的元素不能再使用之外,又添加了一个新的剪枝条件,也就是我们考虑重复部分思考的结果,于是 剪枝条件
- 当当前元素和前一个元素值相同(此处隐含这个元素的 index>0 ),并且前一个元素还没有被使用过的时候,我们要剪枝
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
nums.sort()
self.res = []
check = [0 for i in range(len(nums))]
self.backtrack([], nums, check)
return self.res
def backtrack(self, sol, nums, check):
if len(sol) == len(nums):
self.res.append(sol)
return
for i in range(len(nums)):
if check[i] == 1:
continue
if i > 0 and nums[i] == nums[i-1] and check[i-1] == 0:
continue
check[i] = 1
self.backtrack(sol+[nums[i]], nums, check)
check[i] = 0
递归:对比46题,只需添加判断前后是否相等,若nums[i] == nums[i - 1],则此情况前面已经讨论过,因此continue就好;需要事先对nums进行排序!
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
if not nums: return []
nums.sort()
res = []
def backtrack(nums, tmp):
if not nums:
res.append(tmp)
return
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i - 1]:
continue
backtrack(nums[:i] + nums[i + 1:], tmp + [nums[i]])
backtrack(nums, [])
return res
131. 分割回文串
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
递归方法:
class Solution:
def partition(self, s: str) -> List[List[str]]:
res = []
def helper(s, tmp):
if not s:
res.append(tmp)
for i in range(1, len(s) + 1):
if s[:i] == s[:i][::-1]:
helper(s[i:], tmp + [s[:i]])
helper(s, [])
return res
动态规划方法, 我们用 dp[j][i] 字符串从位置 j 到位置 i(闭区间)是否为回文子串,再用 DFS 把所有可能找到!
class Solution:
def partition(self, s: str) -> List[List[str]]:
n = len(s)
f = [[True] * n for _ in range(n)]
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
f[i][j] = (s[i] == s[j]) and f[i + 1][j - 1]
ret = list()
ans = list()
def dfs(i: int):
if i == n:
ret.append(ans[:])
return
for j in range(i, n):
if f[i][j]:
ans.append(s[i:j+1])
dfs(j + 1)
ans.pop()
dfs(0)
return ret
51. N 皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
if n<1:
return 0
record = [0]*n # rocord[i] 表示第i行的皇后放在了第几列
# res =[]
# cnt= self.process(0,record,n,res)
# print(cnt)
# return res
return self.process(0,record,n)
def process(self,i,record,n):
if i ==n:
return 1
cnt =0
for j in range(n):
# 当第i行的皇后放在第j列是,会不会和之前(0~ i-1)的皇后共行、共列或者共斜线
if self.isValid(record,i,j):
record[i] =j
cnt+= self.process(i+1,record,n)
# tmp = []
# for i in record:
# tmp_str ='.'*n
# tmp_str = tmp_str[:i] +'Q' + tmp_str[i:i+1]
# tmp.append(tmp_str)
# res.append(tmp)
return cnt
def isValid(self,record,i,j):
for k in range(i):
# 判断前i-1个皇后是否放在第j列, 以及是否产生共斜线位置:横坐标的差值等于纵坐标的差值,为共斜线
if j==record[k] or abs(record[k]-j) ==abs(i-k):
return False
return True
优化时间复杂度
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
def generateBoard():
board = list()
for i in range(n):
row[queens[i]] = "Q" #对应位置放上皇后
board.append("".join(row))
row[queens[i]] = "." # 还原
return board
def backtrack(row: int): # row 表示行号
if row == n: # 表示n个皇后的位置已经安排完成
board = generateBoard()
solutions.append(board)
else:
for i in range(n):
# columns 表示前0~row的皇后存放列下表的集合
# row - i,表示皇后所在位置的行列差值, 相同的行列差值表示共左上对角线.
# 相同的行列和表示共右上对角线
if i in columns or row - i in diagonal1 or row + i in diagonal2:
continue
queens[row] = i
columns.add(i)
diagonal1.add(row - i)
diagonal2.add(row + i)
backtrack(row + 1)
columns.remove(i)
diagonal1.remove(row - i)
diagonal2.remove(row + i)
solutions = list()
queens = [-1] * n # 使用一个数组记录每行放置的皇后的列下标
columns = set() # 已经放好的皇后的列下表集合
diagonal1 = set() # 左斜线对应的皇后的横纵坐标的差值
diagonal2 = set() # 右斜线对应皇后的横纵坐标的和
row = ["."] * n
backtrack(0)
return solutions