携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
回溯算法
回溯算法,也叫试探法。他的基本思想是从一种状态出发,搜索从这种“状态”出发能达到的所有状态,当搜索到尽头,回退1-N步,从另外一种可能的状态出发,继续搜索,直到所有的“路径”都走过,这种不断前进,不断回溯(后退)搜索的算法就是回溯法。
leetcode78题(子集)
我们以此经典题目为例,来尝试使用回溯算法
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例:
输入: nums = [1,2,3]
输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
输入: nums = [0]
输出: [[],[0]]
分析
通过分析题目可知几个信息点
- 数组所有子集
- 所有元素互不相同
- 结果不能包含重复
然后还需要我们分析题意,假如我们需要找到所有[1,2,3]的子集,那么要不断的去遍历搜索,从1开始,深度搜索,搜索到1、2,再到1、2、3, 然后再从第二位2开始同层搜索,我们可以看出来结果是个树,题目需要的是所有节点的去重的和
树的展示如下图:
代码
那么有了思路,我们开始写代码,我们先定义返回的结果集ans,这也是个二维数组,还有暂存的路径(path)
const ans = [], path = []
接下来我们要考虑把暂存的路径放到结果集中,通过上面的树可知,每次树的节点遍历,就是每次执行方法的时候(递归),就是所以我们只要是每次方法刚执行的时候,都需要存储数据。
所以我们的回溯方法backtracking这么处理
const backtracking = () => {
// 拷贝path,存储数据进结果集,也可使用ans.push(Array.from(path))
ans.push([...path])
}
然后我们需要同层遍历树
for (let i = 0; i < nums.length; i ++) {
path.push(nums[i]);
backtracking(i + 1);
path.pop();
}
但是由于每次都是从0开始遍历,会导致数据重复,所以我们需要改造一下backtracking方法,增加startIndex,让我们每次搜索的时候都是从上一次传入的index开始
const backtracking = (startIndex) => {
ans.push([...path]);
for (let i = startIndex; i < nums.length; i++) {
path.push(nums[i]);
backtracking(i + 1);
path.pop();
}
};
那么我们完整的代码就出来了
var subsets = function (nums) {
const ans = [],
path = [];
const backtracking = (startIndex) => {
ans.push([...path]);
for (let i = startIndex; i < nums.length; i++) {
path.push(nums[i]);
backtracking(i + 1);
path.pop();
}
};
backtracking(0);
return ans;
};
结语
- 回溯是不得已而为之的方法,也算暴力方法的一种,时间复杂度很高。
leetcode78(子集)这个是标准的回溯问题,我们可以好好把代码理解一下,其他类似的问题都可以用此模板解决- 相似的问题,我们要分析是树的同层处理,还是深度的树处理,如果某些场景下存在重复搜索,我们还需要进行剪枝,减少时间复杂度。