1.题目链接:
77. 组合
2.大概思路:
2.1题目要求:
给两个值 n 和 k ,要求从[1,n]的区间中,输出所有元素数量为k的组合。(不能有[1,1],值只能取一次)
2.2思路:
通过回溯法(递归)来做。(就是按n叉树的方式处理)
一条大目录,大目录里有小目录,一层层递归,到大目录的下一个节点,i+1,遍历完输出结果(输入一个初始值,这里为 1 )
2.3回溯三部曲:(有递归就有回溯)
2.3.1.递归函数的返回值以及参数
无返回值,只是遍历添加元素,参数就是传n和k,需要定义两个数组来存储临时数组和结果集。
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件单一结果
void backtracking(int n, int k, int startIndex)
2.3.2.回溯函数终止条件
到达叶子节点终止(也就是元素个数达到了k),同时把此时临时数组存储的结果输入到结果集。
if (path.size() == k) {
result.push_back(path);
return;
}
2.3.3.单层搜索的过程
因为要遍历大目录,所以用for循环,又因为要遍历大目录里面的小目录,所以要在遍历大目录的for循环里加 递归逻辑,同时在本层加回溯逻辑(返回上来代表下面一层遍历结束),达到终止条件返回,会进行回溯操作,方便小目录节点,记录不同的元素数值,在一个元素确定的情况下,也就是上一层。
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
path.pop_back(); // 回溯,撤销处理的节点
}
2.3.4.总代码:
class Solution {
private:
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
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(); // 回溯,撤销处理的节点
}
}
public:
vector<vector<int>> combine(int n, int k) {
result.clear(); // 可以不写
path.clear(); // 可以不写
backtracking(n, k, 1);
return result;
}
};
3.优化.剪枝操作
3.1.思路
为了方便更好的看出剪枝的效果,设置n=4,k=4,
剪枝的操作是为了去除不必要的遍历过程,如下图,被剪掉的部分,都是不能满足剩余可挑选的元素数量大于等于k的条件的,比如第二层开始,从取2开始已经可以剪掉了,第三层,从取3开始也可以剪掉了,
这也是判断条件。
3.2.操作
理解原理后在哪里操作来完成剪枝?下面是组合里单层搜索过程的代码
for (int i = startIndex; i <= n; i++) {
path.push_back(i);
backtracking(n, k, i + 1);
path.pop_back();
}
修改的就是“ i <=n "这里,修改这里的原因就是想让for循环的范围缩小,遇见被剪枝的范围,就退出for循环。
改成这样
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
" i <= n - (k - path.size()) + 1 " 这是怎么来的?
得出过程如下:
-
已经选择的元素个数:path.size();
-
还需要的元素个数为: k - path.size();
-
这是满足题目条件的不等式:列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size() )
-
化简得i <= n - (k - path.size() ),i 超过这个范围的都是需要被剪掉的,满足这个条件代表,只遍历必要的区域,这样就完成了剪枝操作。
-
最后等式为: i <= n - (k - path.size()) + 1
为啥有个+1?
" i <= n - (k - path.size()) + 1 " 是遍历范围,i 是有起始值的,下面是题目要求
可见起始条件为1(起始点+满足条件的范围)
(感觉还是糊涂x)
4.遇见的问题:
怎么先记录一个,后面再接着记录下一个?
哦哦,进入for(大目录)默认已经记录一个了,后面的递归都是在小目录的for里记录的。
5.记录:
一开始遇见有点懵,理解了一点后,有点不知道该怎么说清楚
其实也不是很难对吧,但没能表述明白,怎么表述明白?我一直没尝试过,都是自己把理解到的思路直接拍上去,先写一遍代码吧,然后再考虑修正的问题。
运行成功!在来看看。好吧,讲不出来。算了,积累是渐进式的,一下加几个有效的模块,感觉作用也不大,都是要一个经历的过程的,虽然会有很多浪费,但没办法,我没法懂...讲解的清楚和思维导图这两个领域暂时触摸不到