回溯的本质
为什么要回溯?首先不要把回溯当作是多么nb的算法,纯纯暴力,最多加一点剪枝进行优化而已。
而回溯算法,其实就是在递归函数中嵌套了for循环而已。我们可以想象一个树形结构,递归就是向下深入便利,而for循环是横向遍历。我喜欢将递归说成树枝遍历,for循环说成树层遍历,更好理解些。
对于回溯的理解
首先附上回溯算法的模板代码:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
从代码中我们看出,想要返回上一层结构,要么通过if(终止条件) return,要么就是全部遍历结束自动返回到上一层递归函数代码backtracking()中。
而问题是——对于不同问题需要设置不同的变量,这些变量不会因为我递归的return而自己撤销操作,所以需要我们手动进行撤销。这就是所谓的回溯。
例如:
组合、排列问题中,都需要构造一个path数组来记录路径,而我如果返回到上一层,数组中的元素不会自动撤回,所以需要path.pop_back()来人工删除。
for(int i=startindex;i<=n;i++)
{
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back();
}
又例如全排列进行树枝去重时,需要构建一个bool数组used来判断元素是否使用过,也不会自动撤回,所以需要重新令used[nums[i]]==false,说明它没有被使用过。
for(int i=0;i<nums.size();i++)
{
if(used[i]==true) continue;//用used判断树枝中是否出现了使用过的元素,出现了就跳过
used[i]=true;
path.push_back(nums[i]);
backtracking(nums,used);
path.pop_back();
used[i]=false;
}
其实,当我们充分理解这两点后,大部分简单、中等的回溯题都可以解决了。
回溯算法中的statindex是什么意思?
我个人认为就是一个定位的作用,实现递归的深层遍历,是否允许重复元素也决定了startindex的位置i是否需要+1,当然在排列过程中,由于顺序是可以多变的,所以每次都是从第一个元素重新开始递归。
树枝去重和树层去重
树枝去重和树层去重都可以运用到used数组,区别在于树层去重是在给数组排序后,若
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false) 就跳过,continue。
而树枝去重则是
if(used[i]==true) continue;
回溯算法能解决如下问题:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
Day30打卡成功,耗时1.5小时