算法模板-回溯
Posted 周先森爱吃素
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法模板-回溯相关的知识,希望对你有一定的参考价值。
简介
回溯法常用于遍历列表所有子集,是 DFS 深度优先搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O ( N ! ) O(N!) O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都比较高。
解题模板
回溯算法的解题一般是递归实现,可以抽象如下。最核心的思路就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通(搜索完成),就返回来撤销这次选择。
result = []
func backtrack(选择列表,路径):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(选择列表,路径)
撤销选择
回溯的题目我们可以理解为一棵树的搜索过程,一方面要考虑树往深度推广(下一层),另一方面也要考虑树往广度推广(这一层)。其实回溯算法关键在于:不合适就退回上一步,然后通过约束条件,,减少时间复杂度。这两个层面正是发生在深度和广度上的。
题目列表
下面是力扣主站题库里典型的回溯求解的题。
题解列表
39-组合总和
这道题我们也可以用回溯的思路来求解所有可能的结果,先选定第i
个元素,然后回溯后面位置的可能组合。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
if not candidates or min(candidates) > target: return []
candidates.sort()
ans = []
def backtrack(candidates, target, tmp=[]):
if target == 0: ans.append(tmp)
if target < 0: return
for i in range(len(candidates)):
if candidates[i] > target:
break
backtrack(candidates[i:], target - candidates[i], tmp + [candidates[i]])
backtrack(candidates, target)
return ans
40-组合总和 II
这道题和上一题类似,区别只是每个数字只能使用一次,因此递归的时候下标要向后一位。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
if not candidates or min(candidates) > target: return []
candidates.sort()
ans = []
def backtrack(i, tmp_sum, tmp=[]):
if tmp_sum == target:
ans.append(tmp)
return
for j in range(i, len(candidates)):
if tmp_sum + candidates[j] > target: break
if j > i and candidates[j] == candidates[j-1]: continue
backtrack(j+1, tmp_sum + candidates[j], tmp + [candidates[j]])
backtrack(0, 0)
return ans
46-全排列
这道题要求生成全排列,组合是排列的子集,排列认为位置不同就不是同一个结果。
这道题和子集那题很像,需要注意写法的区别,子集那题我们不断后移起始位置,但是本题我们始终保证全部位置供选择。
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
47-全排列 II
这道题很上一题类似,我们需要做个去重。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(nums, tmp=[]):
if not nums and tmp not in res:
res.append(tmp)
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], tmp + [nums[i]])
backtrack(nums)
return res
78-子集
这题是要求数组的所有子集,是回溯题中非常经典的题,我们可以很容易地想到,以i
位置为第一个元素加上后续元素的子集。
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
ans = []
def backtrack(start, tmp=[]):
ans.append(tmp)
for i in range(start, len(nums)):
backtrack(i+1, tmp + [nums[i]])
backtrack(0, [])
return ans
90-子集 II
这题排序后去重就行。
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
ans = []
nums.sort()
def backtrack(start, tmp=[]):
if tmp not in ans:
ans.append(tmp)
for i in range(start, len(nums)):
backtrack(i+1, tmp + [nums[i]])
backtrack(0, [])
return ans
补充说明
本文介绍了生成全排列等问题常见的回溯算法,但是解题时回溯用的可能并不多,因为本质上它就是一种暴力算法。
以上是关于算法模板-回溯的主要内容,如果未能解决你的问题,请参考以下文章