前几天刷题遇到了这样一道题:

给定两个数nk要求编码得到1~n中所有可能的k个数的组合

如果k比较小的话(例如<=3),我们是可以用几层循环来穷举所有的可能性的,例如(若k = 3):

1 vector<vector<int>> res;
2 for(int i = 1; i <= n; i++) {
3     for(int j = 1; j <= n; j++) {
4         for(int m = 1; m <= n; m ++) {
5             if(i == j || i == m || j == m)continue;
6             res.push_back({i, j, m});
7         }
8     }
9 }

但是当k未知的时候,他可能是比较大的,这时候如果再想用循环套循环的方法做,可能就无以为继了。

这时候就需要利用回溯算法的思想来解题了:

首先需要定义两个全局变量,一个用于存储所有可能的组合,一个用于存储单一的符合条件的组合:

1 vector<vector<int>> res; //存放所有结果
2 vector<int> tmp; //存放单个结果

接着是回溯函数的主体,其中第三个变量是在n个数中开始遍历的位置:

1 void backtracking(int n, int k, int start) {}

在回溯的过程中,如果tmp的长度等于k的话,就说明这个结果是符合题意的一种组合,就可以将该结果储存到res中去了,并结束该轮的递归:

1 if(tmp.size() == k) {
2     res.push_back(tmp);
3     return;
4 }

然后就是回溯函数中的循环了,首先是将每次循环开始时候的那个数存入到tmp中去,再开始递归回溯,然后再将这个数从tmp中删去,再开始新一轮的循环:

1 for(int i = start; i <= n; i++){
2     tmp.push_back(i);
3     backtracking(n, k, i + 1); //开始递归插入下一个元素
4     tmp.pop_back(); //删除插入的元素并开始新一轮循环
5 }

完整的回溯函数如下所示:

 1 void backtracking(int n, int k, int start) {
 2     if(tmp.size() == k){
 3         res.push_back(tmp);
 4         return;
 5     }
 6     for(int i = start; i <= n; i++) {
 7         tmp.push_back(i);
 8         backtracking(n, k, i + 1); //开始递归插入下一个元素
 9         tmp.pop_back(); //删除插入的元素并开始新一轮循环
10     }
11 }

以n = 4, k = 2为例:

将start设为1,开始循环的时候,1会被插入到tmp中去,并以2为start开始递归,再将2插入到tmp中去,再以3为start开始递归,此时tmp的长度等于k, 为2,因此将{1,2}添加到res中去,并直接return,此时会回到上一步骤中的删除元素中去,将tmp中的2删除,接着开始循环,将3插入tmp中去,以此往复......

最后只要在主函数中调用回溯函数,就可以完成这道题目了:

vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combine(int n, int k) {
    backtracking(n, k, 1);
    return res;
}
void backtracking(int n, int k, int start){
    if(tmp.size() == k){
        res.push_back(tmp);
        return;
    }
    for(int i = start; i <= n; i++){
        tmp.push_back(i);
        backtracking(n, k, i + 1);
        tmp.pop_back();
    }
}

这道题目只是回溯算法中的一道基础题,不过掌握了这种思想的话,再多的难题都能够迎刃而解~

 

 posted on 2021-11-08 16:17  amonyyc  阅读(29)  评论(0)    收藏  举报