回溯算法-数据结构与算法
学习计划:3个月内算法小成,完成leetcode hot100
当前进度:学完数组、链表、哈希表、字符串、双指针、二叉树
刷题语言:Python
时间:2025/05/19-2025/05/21,2025/06/17-
八、回溯算法
学习链接:代码随想录
1、回溯算法理论基础
题目分类
组合 | 组合 |
电话号码的字母组合 | |
组合总和 | |
组合总和II | |
组合总和III | |
分割 | 分割回文串 |
复原IP地址 | |
子集 | 子集 |
子集II | |
全排列 | 全排列 |
全排列II | |
棋盘问题 | N皇后 |
解数独 | |
其他 | 递增子序列(类似子集) |
重新安排行程 |
什么是回溯法?
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯。所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数。
回溯法的效率
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质,所以回溯法并不是什么高效的算法。
那么既然回溯法并不高效为什么还要用它呢?
因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。此时大家应该好奇了,都什么问题,这么牛逼,只能暴力搜索。
回溯法解决的问题
回溯法,一般可以解决如下几种问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
2、组合
题目:给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。你可以按 任何顺序 返回答案。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
思路:把组合问题抽象为如下树形结构,图中可以发现n相当于树的宽度,k相当于树的深度。图中每次搜索到了叶子节点,我们就找到了一个结果。
class Solution:def combine(self, n: int, k: int) -> List[List[int]]:res = []self.backtracking(n,k,1,[],res)return resdef backtracking(self,n,k,startIndex,path,res):#一共有k层for循环,每次递归就是一次for循环if len(path)==k:#终止条件res.append(path[:]) #将 path 的浅拷贝添加到列表 res 中,如果append(path),path变res中的元素也变returnfor i in range(startIndex,n+1):path.append(i) #处理节点self.backtracking(n,k,i+1,path,res)path.pop()#i=1 12 弹出2 | 13 弹出3 | 14 弹出4#i=2 23 弹出3 | 24 弹出4#i=3 34 弹出4
class Solution:def combine(self, n: int, k: int) -> List[List[int]]:res = []path = []def dfs(i):d = k-len(path) #还需要枚举的数字个数if d == 0:#结束递归res.append(path.copy())returnfor j in range(i,0,-1): #倒序枚举path.append(j)dfs(j-1)path.pop()dfs(n)#n=4 43 弹出3| 42 弹出2 | 41 弹出1 结束#n=3 32 弹出2| 31 弹出1 结束#n=2 21 弹出1 结束return res
- 时间复杂度:分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。对于本题,它等于 O(k⋅C(n,k))
- 空间复杂度: O(n)
3、组合(剪枝优化)
class Solution:def combine(self, n: int, k: int) -> List[List[int]]:res = []self.backtracking(n,k,1,[],res)return resdef backtracking(self,n,k,startIndex,path,res):if len(path)==k:#终止条件res.append(path[:])returnfor i in range(startIndex,n-(k-len(path))+2): #优化path.append(i) self.backtracking(n,k,i+1,path,res)path.pop()