回溯法基础知识准备
回溯定义
递归和回溯的区别: 递归回溯相辅相成,递归函数下边的部分即为回溯的逻辑。
回溯搜索法:纯暴力搜索法,有些问题利用暴力搜索法搜不出来,需要使用回溯法进行搜索。
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
这些问题利用一般的for循环是比较难解决的,而使用回溯法可以很好的解决这些问题。
如何理解回溯法
回溯法都可以抽象为一个树形结构,横向使用for循环,纵向使用递归的方式。
回溯法C++伪代码模板
void backtracking(){
if(终止条件){
收集结果,将叶子结点的数值收集到结果集中
return
}
// 单层搜索,for循环对应所有子节点的个数,用来处理相应的结点。
for(集合的元素集){
处理结点
递归函数
撤销结点
}
return
}
77 组合
题目描述: 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
C++伪代码(如何按照上述的结构,将回溯问题进行求解)
//确定递归函数的参数和返回值,一个组合是一个一维数组,将组合进行存放,因而需要将一维数组进行存放
//确定递归的终止条件
//确定单层递归的逻辑
//将组合问题的结构进行实现
二维数组result //全局变量
一维数组path
void backtracking(n,k,startIndex){
// 数组的长度,组合的长度,最后再传入本次要搜索的起始位置。
if(path.size==k){
result.push(path)
return //不需要返回值的话,在终止条件中直接返回return即可
}
// 单层搜索的过程
for(i=startIndex,i<=n,i++){
path.push(i)
backtracking(n,k,startIndex+1)
path.pop(i) // 将之前的元素进行弹出
}
}
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
# 组合问题的求解
result = []
path = []
def backtracking(k,startIndex):
if len(path) == k:
result.append(path.copy())
return
# 回溯法的思路:首先遍历所有的叶子结点,然后再利用相同的方式进行遍历,回溯完成之后将上一步的结果回归的原始状态,确定终止条件,如果题目中没有返回值那么终止条件中直接写return即可。
for i in range(startIndex,n+1):
path.append(i)
backtracking(k,i+1)
path.pop()
backtracking(k,1)
return result
注意: 追加path的结果时,需要进行数组的拷贝,否则最终结果会由于数组只是引用了地址,造成最终的结果发生改变。