算法笔记1【每天一题】
本人小白,第一次写博客,平时经常看题目很少自己动手去敲题目,从今天开始每天进步一点点,尽量坚持每天敲一道题,题目来源于力扣,解题思路来源于代码随想录,会在笔记中写出自己对于题目的一些浅薄的理解,笔记不定期更新,使用的语言是C++。
第1天 组合问题【力扣77题】
遇到类似题目,我会首先考虑使用比较暴力的for循环去进行求解,即在每一层循环当中分别定义i = 1,j = i + 1,k = j + 1…,通过每一层循坏得到组合的结果,当k比较小时,这种方法会比较简单,但是当k满足题目中的条件时,这一类方法很难写出代码。
所以本题使用了回溯算法去进行求解,回溯法对于我来说是一种比较难且不好理解的算法,该算法的本质是穷举,也算是暴力的一种,性能也不是很优秀,但是可以用于很多经典的算法问题,如排列,组合,切割等问题。
理解回溯法
首先我认为可以把回溯问题看作是树相关的问题,因为回溯解决的问题普遍是在集合中递归查找子集,所以我们就可以把树的宽度看作是集合的大小,把树的深度看作是递归的深度。从图中我们可以看到将这个树遍历到叶子节点一般就会得到一个结果,在本题当中,这个点就是我们所需要的一个答案,所以从这里我们差不多可以得出回溯法的简单模板。
void backtracking(参数) {
if (结束条件) {
存结果并且返回;
}
for(横向遍历) {
处理节点;
backtracking(递归);
关键步骤回溯;
}
}
解题过程
有了模板以后的主要难点就是实际操作在题目当中,其余有关于回溯法的更多细节我会继续学习,在日后发出来,那么正式开始!!
1.选择参数:这个部分的选取我个人认为在模板当中是最有难度的,从力扣给出的combine函数我们可以知道结果需要一个二维数组进行输出,即输出的所有结果需要二维数组表示,那么单个结果我们是不是也需要一个一维数组来表述,这样我们就知道除了默认的n,k之外,我们还需要定义两个数组,一个存路径,一个存结果。根据代码随想录的写法,书中以代码可读性为重将两个数组定为全局变量,我也是这么做的,这两个还稍微好理解一些,之后我们还需要有一个变量,我们需要在代码当中使用这个变量去收缩范围,如下图所示,至此第一步结束。
2.终止条件:和递归一样,终止条件十分重要,本题k表示返回k个数的组合,那么是不是说明,当我们的路径和k一样长的时候就可以终止这条路径将结果写入二维数组,及以下的代码。
if(path.size()==k){
result.push_back(path);
return;
}
3.循环递归回溯:循环是指横向遍历,我们要从集合中将【1】【2】【3】【4】开头的情况都考虑到,所以来for循环,for循环中注意i<=n这块,没有等号会少一部分,原因是我们可以在最后的代码部分看到我们combine中的回溯函数是从1开始的,可能大伙都知道,我不知道就当提醒一下自己吧。循环完了记录完路径就开始递归吧,往下走一步,即到达下一层的第一个节点,继续去找路,这里要注意了,组合不能存在【2,1】,【2,2】这种情况,所以我们逛完这个地方也要把垃圾带走,即撤销之前的节点,直到逛完所有地方,拍下所有地方的照片,最后提交就可以了,代码在之后的代码部分给出来了。
代码部分
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void backtracking(int n, int k,int startIndex) {
if(path.size()==k){
result.push_back(path);
return;
}
for(int i=startIndex; i<=n; i++){
path.push_back(i);
backtracking(n, k, i+1);
path.pop_back();//key
}
}
vector<vector<int>> combine(int n, int k){
backtracking(n, k, 1);
return result;
}
};
结语
这是我第一次写博客,内容还是会有很多讲不明白的地方,我个人也比较菜,也希望见证自己可以在未来慢慢变强吧,希望能帮助到看过的人,也推荐大家去看《代码随想录》这本书,里面的对于我来说也是挺有帮助的,本题的视频链接组合问题,如果还有不懂得可以进去看看,谢谢观看。