原问题:构造长为n的字符串->枚举一个字母->子问题:构造长为n-1的字符串 (递归)
回溯有一个增量构造答案的过程通常用递归实现
dfs(i)表示大于等于i的还要枚举
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23" 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = "" 输出:[]
代码:
MAPPING = "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
n = len(digits)
if n == 0:
return []
ans = []
path = [''] * n # 本题 path 长度固定为 n
def dfs(i: int) -> None:
if i == n:
ans.append(''.join(path))
return
for c in MAPPING[int(digits[i])]:
path[i] = c # 直接覆盖
dfs(i + 1)
dfs(0)
return ans
递归过程 :
当digit="23"时,递归过程如下:
初始调用dfs(0)
i = 0,digits[i] = “2”
遍历 MAPPING[2] = “abc”
path[0] = ‘a’
调用dfs(1)
i = 1,digits[i] = “3”
遍历 MAPPING[3] = “def”
path[1] = ‘d’
调用dfs(2)
i = 2,达到n=2,此时path = [‘a’, ‘d’]
将 ‘ad’ 添加到ans中
返回上一层
回到i = 1
遍历 MAPPING[3] = “def”
path[1] = ‘e’
调用dfs(2)
i = 2,达到n=2,此时path = [‘a’, ‘e’]
将 ‘ae’ 添加到ans中
返回上一层
回到i = 1
遍历 MAPPING[3] = “def”
path[1] = ‘f’
调用dfs(2)
i = 2,达到n=2,此时path = [‘a’, ‘f’]
将 ‘af’ 添加到ans中
返回上一层
回到i = 0
遍历 MAPPING[3] = “def”
path[0] = ‘b’
调用dfs(1)
i = 1,digits[i] = “3”
遍历 MAPPING[3] = “def”
path[1] = ‘d’
调用dfs(2)
重复上述步骤,直到遍历完所有可能的组合
最终,返回的ans为[‘ad’, ‘ae’, ‘af’, ‘bd’, ‘be’, ‘bf’, ‘cd’, ‘ce’, ‘cf’]。
1.子集型回溯
每个元素都可以选/不选
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
代码部分:
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ans=[]
path=[]
n=len(nums)
def dfs(i):
if i==n:
ans.append(path.copy())
return
dfs(i+1)
path.append(nums[i])
dfs(i+1)
path.pop()
dfs(0)
return ans
思路
ans接受最终答案,path接收递归过程中的答案,dfs这个函数,首先在i==n的时候,也就是第三次,返回递归得到的值,重点理解
dfs(i+1)
path.append(nums[i])
dfs(i+1)
path.pop()
其实这里第一行应该和第二行第三行是两个单独的模块,第一行表示不选,第二行第三行表示选,并且加进path中,最后pop是恢复现场
什么是恢复现场?为什么要恢复现场
在每一次递归调用结束后,需要将最后选择的元素从路径中移除,以确保路径的正确性。
在递归到某一“叶子节点”(非最后一个叶子)时,答案需要向上返回,而此时还有其他的子树(与前述节点不在同一子树)未被递归到,又由于path为全局变量。若直接返回,会将本不属于该子树的答案带上去,故需要恢复现场。
恢复现场的方式为:在递归完成后 dfs(i+1); 后,进行与“当前操作”相反的操作,“反当前操作”。
什么时候才会执行pop呢,当dfs(n+1)后i==n了,此时直接return,就到了上一个dfs(n+1)那里了,这时候ans已经从path拷贝了一个正确结果,如果这时候path不清空上一个元素,就会导致出错
如果 path 的长度不固定,或者每次递归到 i,path【i】 不一定会被修改,就需要恢复现场。
方法二
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ans=[]
path=[]
n=len(nums)
def dfs(i):
ans.append(path.copy())
if i == n:
return
for j in range(i,n):
path.append(nums[j])
dfs(j+1)
path.pop()
dfs(0)
return ans
第一次dfs(3)时,path此时等于【1,2,3】,在经过三次path后清空,因为dfs(3),dfs(2),dfs(1)后面都有一次pop
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是
回文串
。返回 s
所有可能的分割方案。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
思路:假设每两个相邻字符之间有一个逗号,考虑选还是不选,转化成子集问题
代码
class Solution:
def partition(self, s: str) -> List[List[str]]:
ans=[]
path=[]
n=len(s)
def dfs(i):
if i == n:
ans.append(path.copy())
return
for j in range(i,n):
t=s[i:j+1]
if t=t[::-1]:
path.append(nums[j])
dfs(j+1)
path.pop()
dfs(0)
return ans
组合型回溯
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
ans=[]
path=[]
def dfs(i):
d=k-len(path)
if i<d:
return
if len(path)==k:
ans.append(path.copy())
return
for j in range(i,0,-1):
path.append(j)
dfs(j-1)
path.pop()
dfs(n)
return ans
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
ans=[]
path=[]
def dfs(i,t):
d=k-len(path)
if t<0 or t>(i*2-d+1)*d//2:
return
if i<d:
return
if len(path)==k :
ans.append(path.copy())
return
for j in range(i,0,-1):
path.append(j)
dfs(j-1,t-j)
path.pop()
dfs(9,n)
return ans
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1 输出:["()"]
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
m=2*n
ans=[]
path=['']*m
def dfs(i,open):
if i==m:
ans.append(''.join(path))
return
if open<n:
path[i]='('
dfs(i+1,open+1)
if i-open<open:
path[i]=')'
dfs(i+1,open)
dfs(0,0)
return ans
排列型回溯
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案
示例 1:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
用path记录路径上的数字,s记录剩余未选择的数字
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
n=len(nums)
ans=[]
path=[0]*n
def dfs(i,s):
if i==n:
ans.append(path.copy())
return
for x in s:
path[i]=x
dfs(i+1,s-{x})
dfs(0,set(nums))
return ans
也可以用布尔数组onpath记录在path中的数,num【i】如果在,onpath【i】就为真
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1 输出:[["Q"]]
提示:
1 <= n <= 9
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
ans=[]
col=[0]*n
def vaild(r,c):
for R in range(r):
C=col[R]
if r+c==R+C or r-c==R-C:
return False
return True
def dfs(r,s):
if r==n:
ans.append(['.'*c + 'Q' + '.'*(n-1-c) for c in col])
return
for c in s:
if vaild(r,c):
#也可以把vaild直接加到if中,if all( r+c!=R+col[R] or r-c!=R-col[R] for c in col)
col[r]=c
dfs(r+1,s-{c})
dfs(0,set(range(n)))
return ans
思路col表示n个皇后所在列的位置,col[1]=2表示第二个皇后在第三列,
方法二
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
m = n * 2 - 1
ans = []
col = [0] * n
on_path, diag1, diag2 = [False] * n, [False] * m, [False] * m
def dfs(r: int) -> None:
if r == n:
ans.append(['.' * c + 'Q' + '.' * (n - 1 - c) for c in col])
return
for c, on in enumerate(on_path):
if not on and not diag1[r + c] and not diag2[r - c]:
col[r] = c
on_path[c] = diag1[r + c] = diag2[r - c] = True
dfs(r + 1)
on_path[c] = diag1[r + c] = diag2[r - c] = False # 恢复现场
dfs(0)
return ans