001. 组合

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 " 这是怎么来的?

得出过程如下:

  1. 已经选择的元素个数:path.size();

  2. 还需要的元素个数为: k - path.size();

  3. 这是满足题目条件的不等式:列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size() )

  4. 化简得i <= n - (k - path.size() ),i 超过这个范围的都是需要被剪掉的,满足这个条件代表,只遍历必要的区域,这样就完成了剪枝操作。

  5. 最后等式为: i <= n - (k - path.size()) + 1

为啥有个+1?

 " i <= n - (k - path.size()) + 1 " 是遍历范围,i 是有起始值的,下面是题目要求

 可见起始条件为1(起始点+满足条件的范围)

(感觉还是糊涂x)

4.遇见的问题:

怎么先记录一个,后面再接着记录下一个?

哦哦,进入for(大目录)默认已经记录一个了,后面的递归都是在小目录的for里记录的。

5.记录:

一开始遇见有点懵,理解了一点后,有点不知道该怎么说清楚

其实也不是很难对吧,但没能表述明白,怎么表述明白?我一直没尝试过,都是自己把理解到的思路直接拍上去,先写一遍代码吧,然后再考虑修正的问题。

运行成功!在来看看。好吧,讲不出来。算了,积累是渐进式的,一下加几个有效的模块,感觉作用也不大,都是要一个经历的过程的,虽然会有很多浪费,但没办法,我没法懂...讲解的清楚和思维导图这两个领域暂时触摸不到

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值