回溯的关键是:
for循环里面是对树的每层横向遍历 递归之间是dfs纵向遍历 for循环里把traceback去掉就是这一层横着的遍历,for循环里就是横向该怎么设计
回溯几个要素:终止条件是什么?怎么做选择?选择后怎么pop?traceback传什么参数
什么时候回溯函数里必须要加路径path?
当path中不能包含重复元素的时候
回溯模版
void backtrack(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtrack(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
- 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]
示例 2:
输入:digits = “”
输出:[]
示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]
解题思路
回溯问题的根本其实是树的遍历问题。最终输出的包含各种组合的集合实际上是树的最下面一层的叶子节点。有两种结题的思路:广度优先搜索算法和深度优先搜索算法,题目中只要看到所有组合这种字眼,基本都是广度优先搜索或者深度优先搜索算法了,广度优先搜索算法基本用的就是队列,深度优先搜索算法用的基本都是递归。此题的树如下图所示:所以两种方法无非是用dfs或者bfs遍历这棵树,得到其叶子节点。
第一种解题思路,完全暴力的解法,广度优先搜索的算法,就是利用队列,首先将第一个数字对应的字母放入到队列中,此时将字符串中的这个数字剔除,以此达到结束循环条件的目的,然后逐个弹出队列中的元素,与新的字母想对应,并压入到队列中,如果此时数字字符串空了,则队列中就是所有的字母组合。
例如:digits = 23
abc def
队列:a-b-c-ad-ae-af-bd-be-bf-cd-ce-cf
即a入-b入-c入-a出-ad入-ae入-af入-b出-bd入…可以看到这就是bfs遍历过程
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if not digits: #首先判断特殊情况,如果数字字符串为空
return [] #返回空即可
dic = {} #定义一个哈希表
dic = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno',
'7': 'pqrs', '8': 'tuv', '9': 'wxyz'} #将相应的数字与字母对应起来
l= [] #首先初始化一个list
l.extend(dic[digits[0]]) #首先将数字字符串中的第一个数字对应的几个字母压入到队列中 这里注意extend(dic[digits[0])是把dic[digits[0]这个list的每个元素拆开全部添加到l的尾部,如dic[digits[0]='abc' 那么l.extend(dic[digits[0]])加入的就是['a','b','c']
digits = digits[1:] #将这个数字从字符串中移除,为了方便构建结束条件
while digits: #当数字字符串不为空时,此时表示还需要有新的字母组合
for i in range(len(l)): #因为每个数字对应的字母均要用来进行字母组合,所以这边需要知道每一层的字母个数
tmp = l.pop(0) #首先将第一个元素弹出
for j in range(len(dic[digits[0]])): #将第一个元素弹出,此时需要与第二个数字对应
#的字母进行组合,每个字母均需要,所以这边要按照长度进行遍历
tmp1 = tmp + dic[digits[0]][j] #将弹出的元素和新的字母进行组合
l.append(tmp1) #组合后放入队列中
digits = digits[1:] #将这个已经组合完成的数字移除
return l #返回结果即可
解法2:
回溯
class Solution(object):
def letterCombinations(self, digits):
dic = {} #定义一个哈希表
dic = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno',
'7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
path=[]
ans=[]
def traceback(path,digits,i):
if len(path)==len(digits):
ans.append(''.join(path[:]))
return
for j in range(len(dic[digits[i]])):
path.append(dic[digits[i]][j])
traceback(path,digits,i+1)
path.pop()
return
traceback(path,digits,0)
return ans
- 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
解题思路
一种是找规律,一种是DFS回溯。找规律方法可以发现,n=1时是(),n=2时是()(),(()) 可以发现每新增一对括号,就是在上一次的结果的各个位置插入一个"()",最后每一趟的结果用集合set()防止重复。代码如下
class Solution:
def generateParenthesis(self, n):
ans=['']
for _ in range(n):
tmp = set()
for s in ans:
for i in range(len(s)+1):
tmp.add(s[:i]+'()'+s[i:]) # 在上一次的结果的所有字符串的各个位置上插入'()'
ans=tmp
return list(ans)
回溯方法:
解题思路
回溯的灵魂就是画出树结构图。
可以发现这其实就是一个满二叉树,我们只需要DFS所有节点即可。接下来先把最基本的实现,然后再去改进。所以先把所有组合给生成出来,即DFS所有节点。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0: return []
res = []
def dfs(paths):
if len(paths) == n * 2: # 因为括号都是成对出现的
res.append(paths)
return
dfs(paths + '(')
dfs(paths + ')')
dfs('')
return res
输出的结果如下
[‘((((’, ‘((()’, ‘(()(’, ‘(())’, ‘()((’, ‘()()’, ‘())(’, ‘()))’, ‘)(((’, ‘)(()’, ‘)()(’, ‘)())’, ‘))((’, ‘))()’, ‘)))(’, ‘))))’]
我们发现有一些结果是我们不需要的,比如((((,比如))))
观察不需要的括号特点,((((实际上已经超过n了,我们生成同一方向的括号只需要n个即可,在生成的时候我们要限制住左括号与右括号生成的数量
这时我增加了left与right参数,分别代表左括号与右括号的数量,每生成一个我就增加一个。
那结束DFS的条件首先就需要把不符合的给过滤掉, ( > n 或 ) > n 或 ) > (
当然 ) > n 这个条件也可以没有,因为 ) > ( 条件已经给控制住了。
def dfs(paths, left, right):
if left > n or right > left: return
if len(paths) == n * 2: # 因为括号都是成对出现的
res.append(paths)
return
dfs(paths + '(', left + 1, right) # 生成一个就加一个
dfs(paths + ')', left, right + 1)
DFS最终代码
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
if n <= 0: return []
res = []
def dfs(paths, left, right):
if left > n or right > left: return
if len(paths) == n * 2: # 因为括号都是成对出现的
res.append(paths)
return
dfs(paths + '(', left + 1, right) # 生成一个就加一个
dfs(paths + ')', left, right + 1)
dfs('', 0, 0)
return res
**
全排列
**
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
方法:回溯
class Solution(object):
def permute(self, nums):
path=[]
ans=[]
used=[False]*len(nums)
def traceback(path,nums,used):
if len(path)==len(nums):
ans.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
path.append(nums[i])
used[i]=True
traceback(path,nums,used)
path.pop()
used[i]=False
return
traceback(path,nums,used)
return ans
子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
方法:回溯
class Solution(object):
def subsets(self, nums):
ans=[]
path=[]
ans.append(path[:])
def traceback(nums,path,start):
for i in range(start,len(nums)):
path.append(nums[i])
ans.append(path[:])
traceback(nums,path,i+1)
path.pop()
return
traceback(nums,path,0)
return ans