之前用回溯法解决了子集,排列,组合等问题,现在总结一下回溯算法:
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
回溯法实际是对于一颗树的深度优先搜索,在每次搜索一种状态时都要判断该种状态是否满足条件,如果已经不满足,则可以直接剪枝处理;如果当前状态不满足,下一个状态可能满足,则继续搜寻下一个状态。
其实现要点如下:
1.根节点的确定
2.从某一节点出发的子节点的可能情况
3.判断当前状态是否符合条件
4.判断是否可以剪枝
5.根据当前节点的子节点情况进行遍历,选取第一个子节点,保存下一个状态,递归下一个状态判断,回退到当前状态,进行第二个节点判断,依次进行直到所有子节点遍历完成
6.去重的判断:一般将某一节点的所有子节点排序,然后遍历某一子节点,如果前一个节点与本节点相同,则可以直接跳过,搜寻下一个子节点
Given a set of distinct integers, nums, return all possible subsets (the power set).
class Solution:
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
rr=[]
self.backsubsets(rr, [], 0, nums)
return rr
def backsubsets(self, rr, r, index, nums):
rr.append(r.copy())
print(rr)
for i in range(index, len(nums)):
r.append(nums[i])
self.backsubsets(rr, r, i+1, nums)
r.pop(-1)
深度优先搜索树如下所示:
Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).
class Solution:
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
rr=[]
nums.sort()
self.backtrack(rr, [], 0, nums)
return rr
def backtrack(self, rr, r, index, nums):
rr.append(r.copy())
for i in range(index, len(nums)):
if i>index and nums[i]==nums[i-1]:
continue
r.append(nums[i])
self.backtrack(rr, r, i+1, nums)
r.pop(-1)
要注意去重,当遍历到某一节点,分析其子节点的各自取值时,由于原数组已经排序,因此相同元素此时已经相邻,分析第一个子节点直接进入下一个状态判断,分析第二个子节点时,如果第二个子节点和第一个相同,则应该直接跳过第二个子节点,因此去重的判断条件是
i>index and nums[i-1]==nums[i]
深度优先搜索树:
Given a collection of distinct integers, return all possible permutations.
class Solution:
def permute(self, nums=[1,2,3]):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
rr = []
self.backtrace(rr, [], nums)
return rr
def backtrace(self, rr, r, nums):
if len(r)==len(nums):
rr.append(r.copy())
return
for i in range(0, len(nums)):
if nums[i] in r:
continue
r.append(nums[i])
self.backtrace(rr, r, nums)
r.pop(-1)
深度优先搜索树:
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
class Solution:
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
rr = []
flag = [False for v in range(len(nums))]
nums.sort()
self.backtrace(rr, [], flag, nums)
return rr
def backtrace(self, rr, r, flag, nums):
if len(r)==len(nums):
rr.append(r.copy())
return
for i in range(0, len(nums)):
if flag[i] or (i>0 and nums[i-1]==nums[i] and flag[i-1]):
continue
r.append(nums[i])
flag[i] = True
#print(r)
self.backtrace(rr, r, flag, nums)
r.pop(-1)
flag[i] = False
深度优先搜索树:
Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.The same repeated number may be chosen from candidates unlimited number of times.
class Solution:
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
rr=[]
self.backtrace(rr, [], 0, candidates, target, 0)
return rr
def backtrace(self, rr, r, s, candidates, target, start):
if s==target:
rr.append(r.copy())
return
if s>target:
return
for i in range(start, len(candidates)):
r.append(candidates[i])
s += candidates[i]
self.backtrace(rr, r, s, candidates, target, i)
s -= candidates[i]
r.pop(-1)
深度优先树:
Given a collection of candidate numbers (candidates
) and a target number (target
), find all unique combinations in candidates
where the candidate numbers sums to target
.Each number in candidates
may only be used once in the combination.
class Solution:
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
rr=[]
candidates.sort()
self.backtrace(rr, [], 0, candidates, target, 0)
return rr
def backtrace(self, rr, r, s, candidates, target, start):
if s == target:
rr.append(r.copy())
return
if s > target:
return
for i in range(start, len(candidates)):
if i>start and candidates[i-1]==candidates[i]:
continue
r.append(candidates[i])
#print(r)
s += candidates[i]
#print(s)
self.backtrace(rr, r, s, candidates, target, i+1)
r.pop(-1)
s -= candidates[i]
Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
class Solution:
def combinationSum3(self, k, n):
"""
:type k: int
:type n: int
:rtype: List[List[int]]
"""
rr=[]
self.backtrace(rr, [], 0, k, 1, n)
return rr
def backtrace(self, rr, r, s, k, start, n):
if len(r)==k and s==n:
rr.append(r.copy())
return
if s>n or len(r)>k or (len(r)==k and s!=n):
return
for i in range(start, 10):
r.append(i)
s += i
self.backtrace(rr, r, s, k, i+1, n)
r.pop(-1)
s -= i
回溯法实现了对于集合按照某种规则进行遍历所有情况,当遍历到某一种情况时要判断搜寻条件是否符合,以及根据当前节点判断其子节点的情况。
回溯法一般处理步骤:
声明保存所有树节点状态集合rr
声明用来保存搜寻过程中的节点保存变量r
确定根节点,一般从空集开始[]
判断当前状态r是否符合条件,符合则复制r一份加入到rr中
如果不符合,并且继续搜寻子节点也必定不符合,则直接返回
分析当前节点的左右可能子节点:
根据子节点的取值,判断是否去重,是否直接进行下一个子节点的搜寻
将子节点加入到r中,更新相关变量
进行子节点的递归搜寻
将子节点从r中删除,进行下一个子节点的分析。