回溯算法框架
回溯法和DFS息息相关。
回溯是“撤回一步”的意思
解决一个回溯问题,实际上就是一个决策树的遍历过程。只需要思考3个问题:
- 路径:已经做出的选择;
- 选择列表:当前可以做的选择;
- 结束条件:到达决策树底层,无法再做选择的条件。
res = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
核心:递归调用前做选择,调用后撤销选择
经典问题
排列-Permutation
遍历到树的底层,路径就是一个全排列。多叉树的遍历框架:
def traverse(root):
for(root.child):
#前序遍历需要的操作
traverse(child)
#后序遍历需要的操作
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
定义k:对下标为k的元素做决策
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if not nums:
return []
res = []
self.backtrack(res,0,nums)
return res
def backtrack(self,res,k,nums):
if k==len(nums):
res.append(nums[:])
return
for i in range(k,len(nums)):
nums[i],nums[k] = nums[k],nums[i]#选择nums[i]
self.backtrack(res,k+1,nums)#k+1,把nums[i]加入路径
nums[i],nums[k] = nums[k],nums[i]#撤销选择
子集-Subsets
78. 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
if not nums:
return []
curr = []
res = []
self.backtrack(curr,res,0,nums)
return res
def backtrack(self,curr,res,k,nums):
if k==len(nums):
res.append(curr[:])
return
#不选nums[k]
self.backtrack(curr,res,k+1,nums)
#选nums[k]
curr.append(nums[k])
self.backtrack(curr,res,k+1,nums)
curr.pop()
组合-Combination
77. 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
curr = []
res = []
self.backtrack(curr,res,1,k,n)
return res
def backtrack(self,curr,res,i,k,n):
if len(curr)==k:
res.append(curr[:])
return
if i>(n-(k-len(curr)))+1:#剪枝
return
self.backtrack(curr,res,i+1,k,n)
curr.append(i)
self.backtrack(curr,res,i+1,k,n)
curr.pop()
剪枝:以n=4,k=2为例
当curr=[]时,还需要至少有2个元素可供选择,k-len(curr)
如果i>3则一定不能产生2个数的组合,(n-(k-len(curr)))+1
去重策略
有重复元素的排列问题
47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
以[2,2,3]为例,如果有多个2候选,那么只能选第一个2,后面的2此次不能选。
set()辅助判断
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
if not nums:
return []
res = []
self.backtrack(nums,0,res)
return res
def backtrack(self,nums,k,res):
if k==len(nums):
res.append(nums[:])
return
seen = set()
for i in range(k,len(nums)):
if nums[i] not in seen:
seen.add(nums[i])
nums[i],nums[k] = nums[k],nums[i]
self.backtrack(nums,k+1,res)
nums[i],nums[k] = nums[k],nums[i]
有重复元素的子集问题
90. 子集 II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
if not nums:
return []
**nums.sort()**
res = []
curr = []
self.backtrack(nums,curr,0,res)
return res
def backtrack(self,nums,curr,k,res):
if k==len(nums):
res.append(curr[:])
return
#不选nums[k],则与其相等的元素都不选
i = k+1
while i<len(nums) and nums[i]==nums[k]:
i+=1
self.backtrack(nums,curr,i,res)
curr.append(nums[k])
self.backtrack(nums,curr,k+1,res)
curr.pop()