第二十四天| 第七章 回溯算法part01 理论基础 77. 组合
一、理论基础
1.1 解决的问题
组合、切割、子集、排列、棋盘
1.2 什么是回溯法
-
回溯法也可以叫做回溯搜索法,它是一种搜索的方式。
-
回溯是递归的副产品,只要有递归就会有回溯。
-
所以以下讲解中,回溯函数也就是递归函数,指的都是一个函数。
1.3 回溯法的效率
-
虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法。
-
因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案
1.4 如何理解回溯法
-
回溯法解决的问题都可以抽象为树形结构
-
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。
-
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
1.5 回溯算法模板
- 回溯三部曲
- 回溯函数返回值及参数
- 回溯算法中函数返回值一般为void。
- 因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。
- 回溯函数终止条件
- 什么时候达到了终止条件,树中就可以看出,一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
- 回溯搜索遍历过程
- 回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成了树的深度。
- 如下图所示:可以发现,for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历
- 回溯函数返回值及参数
最终的模板如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
二、77. 组合
-
题目链接:https://leetcode.cn/problems/combinations/
-
题目介绍:
-
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。你可以按 任何顺序 返回答案。
-
-
思路:
- 按照回溯算法模板来写,主要是体会回溯的过程,这个思路有点类似于二叉树全路径那道题。
- 使用一个path记录路径,只要path.size() == k,就把path加入result中。
-
代码:
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new ArrayList<List<Integer>>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n, k , 1);
return result;
}
// 1. 确定递归(回溯)函数的参数和返回值
// 1.1 返回值:回溯算法一般没有返回值,即void
// 1.2 参数:n和k是必须的,在后续分析的过程中可以得知,我们需要一个startIndex用于控制集合的开始。(因为:组合问题元素是不能重复取的,并且是无序的, 这里只能再继续选比当前元素更大的,所以backtrack的列表中有startIndex这个参数)
public void backtracking(int n, int k, int startIndex) {
// 2. 终止条件
if (path.size() == k) {
// 2.1 存放结果
// 一定要注意,向一个嵌套List的列表中add列表的时,这个元素必须要new出来
result.add(new ArrayList<>(path));
return;
}
// 3. 单层搜索逻辑
// 3.1 遍历集合
for (int i = startIndex; i <= n; i++) {
// 3.2 处理节点
path.add(i);
// 3.3 递归
backtracking(n, k, i + 1);
// 3.4 回溯
path.removeLast();
}
// 执行结束, 回溯,找到上一层的调用函数
// 这一步不是非必须的,因为函数调用底层是栈的数据结构,执行结束会自动溯源
return;
}
}