本文讲解一下回溯算法的相关内容。
1.回溯算法的概念
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。因此,回溯的效率并不高,常常伴随着剪枝的操作。
在解决回溯问题的时候,我们可以将其抽象为树形结构。从根节点开始逐层做出选择,这期间可以伴随着剪枝操作(优化),直到树形底层无法做出选择即为我们想要搜索出的路径结果。基于此,引出下面回溯算法的模板。
2.回溯算法模板
这里借用labuladong 回溯框架进行讲解。考虑的三个问题:
- 路径:也就是已经做出的选择。
- 选择列表:也就是你当前可以做的选择。
- 结束条件:也就是到达决策树底层,无法再做选择的条件。
基于上述三个问题,给出下面框架的伪代码
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
注意:个人认为,最重要的是确立好行列所代表的信息:行做循环(选择),列做递归(基于这个选择到下一层做出另一个选择,以此类推,直至到达结束条件)。下面我们进行算法题讲解:
3.算法题讲解
1)全排列【难度中等】
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列。你可以 按任意顺序 返回答案。
示例
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
数据范围
1 <= nums.length <= 6-10 <= nums[i] <= 10nums中的所有整数 互不相同
-
如上图,每行确立的是目标排列的第几位数字;如果不加约束的话,每列都从1-3进行遍历循环
-
很显然,题目要求排列结果中不能含有重复的数字,因此我们就需要在递归过程中判断在选择列表中是否有元素已经包含在当前路径中(虚线标记的路径是不符合规则的)。
-
如何判断是否存在重复元素呢?
在传入回溯方法参数中添加一个
Set,利用其判重,随着回溯的过程进行添加&删除当前元素。
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
int n = nums.length;
backtracking(nums, 0, n, new LinkedList<>(), new HashSet<>());
return res;
}
void backtracking(int[] nums, int startIndex, int n, LinkedList<Integer> list, Set<Integer> visited) {
// 判断:结束条件(到达了叶子节点,在搜索过程中不符合条件的路径已经被剪枝了,因此能够到达叶子节点的都是符合要求的)
if(startIndex == n) {
res.add(new ArrayList<>(list));
return;
}
for(int i = 0; i < n; i++) {
// 如果使用过该元素,则直接跳过
if(visited.contains(nums[i])) continue;
// 添加标记
visited.add(nums[i]);
// 当前节点首次进入,添加到「路径」中
list.addLast(nums[i]);
// 进入下一层
backtracking(nums, startIndex + 1, n, list, visited);
// 即将选择下一个元素,将当前元素移除
list.removeLast();
// 取消标记
visited.remove(nums[i]);
}
}
}
声明:
- 本文仅仅作为个人学习笔记;
- 本文参考了labuladong算法小抄;