组合问题-回溯算法

news/2024/11/24 4:47:46/

1题目

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

2链接

题目链接:77. 组合 - 力扣(LeetCode)

视频链接:带你学透回溯算法-组合问题(对应力扣题目:77.组合)| 回溯法精讲!_哔哩哔哩_bilibili

3解题思路

第一反应找两个数的组合,两层for循环暴力求解,此题可行。

但是如果找50个数的组合,嵌套50层for循环根本不可能,时间复杂度极高,所以要用回溯算法

可以把这种问题都看成树状结构问题,如图所示:

每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围

图中可以发现n相当于树的宽度,k相当于树的深度

图中每次搜索到了叶子节点,我们就找到了一个结果

引入类似递归三部曲的回溯算法三部曲

1、确定函数返回值及参数

本题可以使用全局变量来记录结果,无需函数返回值。

注意到组合的特性:无顺序。所以选取的时候不能再反向选取,我们可以通过设定一个标志位startIndex来处理

vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件单一结果
void backtracking(int n, int k, int startIndex)

2、回溯的终止条件

如何终止?答:到达叶子结点(找到了目标组合)

其实也就是path的长度等于所需长度k

此时用result二维数组,把path保存起来,并终止本层递归。

if (path.size() == k) {result.push_back(path);return;
}

3、单层回溯的过程

回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。

如此我们才遍历完图中的这棵树。

for循环每次从startIndex开始遍历,然后用path保存取到的节点i。

for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历path.push_back(i); // 处理节点backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始path.pop_back(); // 回溯,撤销处理的节点
}

可以看出backtracking(递归函数)通过不断调用自己一直往深处遍历,总会遇到叶子节点,遇到了叶子节点就要返回。

注意下重点在于后面的撤销操作,这样才能起到回溯的效果


接上述内容

我们进行回溯求解时会发现,有很多树枝根本不可能产生结果,所以引入了新的优化方法:剪枝

举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在

第二层for循环,从元素3开始的遍历都没有意义了。如图:

说白了就是更改for循环的判别条件

思考一下整个过程:

1、我们使用一维数组path记录已选择的内容,已选择的个数为path.size()

2、我们所需的元素个数还剩:k-path.size()

3、列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

4、在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历。为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

所以优化后的for循环是:

for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置

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;}// i为本次搜索的起始位置for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {//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;}
};


http://www.ppmy.cn/news/78254.html

相关文章

一周吃透Java面试八股文(2023最新整理

Java就业大环境仍然根基稳定&#xff0c;市场上有很多机会&#xff0c;技术好的人前景就好&#xff0c;就看你有多大本事了。小编得到了一份很不错的资源&#xff0c;建议大家可以认真地来看看以下的资料&#xff0c;来提升一下自己的核心竞争力&#xff0c;在面试中轻松应对面…

Java之运算符

&#xff0b;加号的作用 1.表示正数 2.相加运算符 3.进行字符串的拼接 4.自增 Tips&#xff1a; 运算运算符优于 扩展赋值运算符 byte a ; int b ; ab&#xff1b; 右侧为byte&#xff0c;无需强制转换 aab; 右侧为int&#xff0c;需强制转换为byte&#xff0c;赋给左边…

如何进行远程adb真机调试?

进行远程adb真机调试可以分为以下几个步骤&#xff1a; 1. 确保远程设备连接到网络并已开启开发者选项&#xff0c;USB调试以及允许通过网络进行ADB。 2. 获取远程设备的IP地址。 3. 在电脑端打开CMD或Terminal等终端&#xff0c;输入以下两行命令&#xff1a; adb tcpip 555…

【3】模型相关函数及构建二维线性模型

1 保存和恢复模型 1.1保存模型 tf.train.Saver()函数可以建立一个saver对象&#xff0c;然后在session中调用save即可将模型保存起来。 # 导入tensorflow类库 import tensorflow as tfv1 tf.Variable(tf.constant([[5.0, 6.0], [7.0, 7.0]], shape[2, 2]), name"m1&quo…

CSDN MD编辑器跳转方法及字体格式

一、点击关键语句跳转指定位置 在CSDN写文章的时候&#xff0c;写的文章过长往往会让读者很难找到自己想看的部分&#xff0c;这时候有个 跳转到指定位置功能 就非常的便利。CSDN在MD编辑器上(富文本编辑器只有一种)就提供了两种跳转到指定位置的方法&#xff1a; 一、目录跳转…

使用 Kafka Assistant,为您的开发加速

简要介绍 快速查看所有 Kafka 集群&#xff0c;包括Brokers、Topics和Consumers支持各种认证模式&#xff1a;PLAINTEXT、SASL_PLAINTEXT、SSL、SASL_SSL对Kafka集群进行健康检查查看分区中的消息内容并添加新消息查看消费者订阅了哪些主题&#xff0c;以及分区被分配给了哪些…

如何实现自我管理?

作者 | Stefan Wolpers 自我管理是一个组织实现业务敏捷性的重要组成部分&#xff0c;还是一种很好的文化转变&#xff0c;例如&#xff0c;让团队快乐并吸引新人才。 虽然很多人&#xff0c;尤其是管理层的人&#xff0c;对这一概念持怀疑态度&#xff0c;但我相信&#xff…

月薪10k和月薪25k的软件测试人员有什么区别?看完你就不会再迷茫了

了解软件测试这行的人都清楚&#xff0c;功能测试的天花板可能也就15k左右&#xff0c;而自动化的起点就在15k左右&#xff0c;当然两个岗位需要掌握的技能肯定是不一样的。 如果刚入门学习完软件测试&#xff0c;那么基本薪资会在7-8k左右&#xff0c;这个薪资不太高主要是因…