Leetcode - 77 : 组合
这道题我觉得是对回溯进行深入了解并运用的很好的题目,看完这道题的题解并亲手写出来之后就感觉挺通透的。这道题我觉得最深刻的还是下面这张图:
将这个组合问题想成用dfs的思想来处理这棵树的形式就理解了,dfs就是在遍历到头之前都不回头但在每次移动的过程之后会标记也就是回溯当前节点到未处理状态,但是回溯到了未处理状态也并不会在重蹈覆辙一直比昂立第一个节点,而是在for循环中进行了i++的操作,向后移了一个节点,从下一个节点再次开始遍历,寻找未处理过的节点来匹配。也就是第一次选了1、2,接着第二次就会选2、1,这样就得到了不同的排列组合数。
这道题我们就根据上图一步一步来进行拆分理解:首先我们是从[1, n]的闭区间内找到元素数量为k的不同的排列,所以我们的第一层for循环用来确定第一个元素。接下来寻找第二个元素也是跟上面的过程一样,这里我们就来选择递归的方式来进行。
回溯三部曲:
(一)我们需要确定参数和返回值:
首先我们的基础参数n, k需要传入因为我们选取元素是每一次的for循环都要用到[start, n]这个闭区间。接着而k是我们需要确定的元素个数也是我们终止递归的重要标志也需要传入。接着我们要思考由于每一次选取元素采用的for循环都是进行顺序查找,也就是进行i++的操作,所以我们在选取玩第一号元素后,记着在继续选取2、3、4、...、k时我们就需要从下一个元素开始选取遍历。所以我们需要记录当前遍历到的元素的下标。
这里需要注意的是:在最开始介绍dfs的过程中所提到的是求排列数,二这道题要我们求的是组合数,所以我们需要记录下标但没有记录处理状态,因为已经跳过了前面的元素,而处理过的元素都在前面的区间内。而排列数我们要从头遍历,当遍历到已处理的节点是就要跳过。
(二)回溯函数终止条件:
这里的终止条件就是path路径数组内的元素个数达到了k个就跳出递归直接返回
(三)单层搜索的过程:
这里搜索的过程就是从1开始往后遍历元素。
class Solution {
public:
vector<int> path;
vector<vector<int>> res;
void solve(int start, int n, int k){
if(k == path.size()){
res.push_back(path);
return;
}
for(int i = start; i <= n; i++){
path.push_back(i);
solve(i + 1, n, k);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
solve(1, n, k);
return res;
}
};
剪枝:
当我们遇到上图这种情况是可以采取剪枝的操作,也是由于组合的特殊性不会选取前面的元素的特性,当当前元素后的元素数量+1(加上本元素)的值小于k时直接break当前循环因为当前元素不行后面的更不行了,因为元素数更少了。所以在for循环遍历元素的过程中在判断一下元素数量即可。
class Solution {
public:
vector<int> path;
vector<vector<int>> res;
void solve(int start, int n, int k){
if(k == path.size()){
res.push_back(path);
return;
}
for(int i = start; i <= n; i++){
if(n - i + path.size() + 1 < k) break;
path.push_back(i);
solve(i + 1, n, k);
path.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
solve(1, n, k);
return res;
}
};