回溯算法:
回溯算法是一种通过探索所有可能的候选解来找出所有解的问题的算法。如果候选解被确认不是一个有效的解或者不可能产生一个有效的解,回溯算法会丢弃该候选解。这种算法经常用于解决组合问题,如图的遍历、数独、八皇后问题等。
基本原理
- 选择:从候选解中选择一个可能的分支。
- 约束:检查这个分支是否符合约束条件,如果不符合,则剪枝(即放弃这个分支)。
- 目标:检查是否达到了问题的解,如果达到了,则记录这个解。
- 回溯:返回上一步,撤销最近的选择,并尝试另一个分支。
算法步骤
- 初始化:定义一个空的解集。
- 从根开始:从问题的根部开始,探索可能的候选解。
- 深度优先搜索:对每一步,尝试所有可能的选项,深入到下一层。
- 检查约束:在每一步检查约束条件,如果当前分支不能产生有效解,就回溯。
- 记录解:当找到一个有效解时,将其添加到解集中。
- 回溯继续:回溯到上一步,尝试其他分支。
特点
- 时间复杂度:通常很高,因为它尝试所有可能的解决方案,但通过剪枝可以大幅度减少搜索空间。
- 空间复杂度:由于需要存储递归函数的调用栈,空间复杂度也可能很高。
- 适用性:适合于问题的解空间是离散且有限的情况。
应用实例
- 八皇后问题:在8×8的棋盘上放置八个皇后,使得它们互不攻击。
- 数独:填充9×9的网格,使得每一行、每一列和每一个3×3的子网格中的数字1到9不重复。
- 图的遍历:在图结构中寻找特定的路径或者遍历所有的节点。
实现技巧
- 使用递归来实现深度优先搜索。
- 在递归之前和之后加入检查和回溯的逻辑。
- 使用剪枝技术来减少不必要的搜索。
回溯算法的关键在于找到有效的剪枝条件,这样可以显著减少搜索空间,提高算法的效率。
77. 组合
class Solution:
def backtracking(self, n, k, start):
if len(self.path) == k:
self.res.append(self.path[:])
return
for i in range(start, n + 1):
self.path.append(i)
self.backtracking(n,k,i + 1)
self.path.pop()
def combine(self, n: int, k: int) -> List[List[int]]:
self.res = []
self.path = []
self.backtracking(n,k,1)
return self.res
剪枝思路1:在for循环中,实际不需要到末尾,只要到窗口右端碰到n的末尾即可。
class Solution:
def backtracking(self, n, k, start):
if len(self.path) == k:
self.res.append(self.path[:])
return
for i in range(start, n - (k - len(self.path)) + 2):
self.path.append(i)
self.backtracking(n,k,i + 1)
self.path.pop()
def combine(self, n: int, k: int) -> List[List[int]]:
self.res = []
self.path = []
self.backtracking(n,k,1)
return self.res
剪枝思路2:如果当前组合的长度加上从当前数字到 n 的数字总数仍然小于 k,那么就没有必要继续探索这个分支。
if len(path) + (n - start + 1) < k:
return
今日总结:
学习了回溯算法,有点晕掉,回滚几次多理解吧..