【回溯+剪枝】组合问题!

news/2025/2/1 6:41:23 标签: 剪枝, 深度优先, 算法, c++

文章目录

  • 77. 组合
  • 解题思路:回溯
  • 剪枝优化

在这里插入图片描述

77. 组合

77. 组合

​ 给定两个整数 nk,返回范围 [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]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

解题思路:回溯

​ 这道题直接用暴力的话会发现是超时,但是时间复杂度其实是非常高的,我们需要套 kfor 循环!所以这种组合的问题就很适合用回溯来解决,特别是当其是组合!

​ 把组合问题抽象为如下树形结构:

在这里插入图片描述

​ 接下来就是回溯三部曲:

  1. 函数头设计
    • 因为我们最后要返回一个 vector<vector<int>> ,那么期间我们也得有一个 vector<int> 来记录当前符合条件的结果
    • 除此之外,为了防止出现重复的组合,我们需要一个 cur 变量,比如这次是 [1, 2, 3] 中取 1,那么下一层递归中就要从 2 开始取,不然就会出现 11 的情况!所以需要 curindex 来记录下一层递归,搜索的起始位置。
  2. 递归函数出口
    • 通过这道题我们很清楚的知道终止条件就是要判断这个最后结果的位数也就是 k,那么我们只需要判断 v 数组中的长度是否等于 k,等于说明已经满足了,就不需要再向下递归了,则将当前的结果集放到 ret 中,然后返回即可。
  3. 函数体内容
    • 回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出 for 循环用来横向遍历,而递归的过程是纵向遍历。
      在这里插入图片描述

    • 如此我们才遍历完图中的这棵树。for 循环每次从 cur 开始遍历,然后用 path 保存取到的节点。

    • 并且不要忘记在途中递归向下取下一个数字的时候,返回之后,我们还需要继续一个回溯,也就是将 path 中刚才递归下去的那层的那个 i 去掉,这是为了防止我们取不到下下个数字,比如说 [1, 2, 3] ,我们现在取了 1,并且先继续插入到 path 中,然后我们递归到下一层取了 [1, 2],此时 2 也会被 pushpath 中,那么返回回来的时候如果不将 2 pop掉的话,path 就还是 k = 2,那么递归下一层的时候就直接返回了,就取不到 [1, 3] 了!

class Solution {
    vector<vector<int>> ret; // 存放结果集
    vector<int> path;        // 存放当前路径中的元素
public:
    vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);
        return ret;
    }

    void dfs(int n, int k, int cur)
    {
        // 递归函数出口
        if(path.size() == k)
        {
            ret.push_back(path);
            return;
        }

        for(int i = cur; i <= n; ++i)
        {
            // 处理当前节点
            path.push_back(i);

            // 递归处理当前节点后面的路径
            dfs(n, k, i + 1);

            // 回溯处理
            path.pop_back();
        }
    }
};

剪枝优化

​ 我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。在遍历的过程中有如下代码:

for(int i = cur; i <= n; ++i)
{
    path.push_back(i);
    dfs(n, k, i + 1);
    path.pop_back();
}

​ 这个遍历的范围是可以剪枝优化的,怎么优化呢?

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

​ 这么说有点抽象,如图所示:

在这里插入图片描述

因为我们要的 k4,但是从 2 开始的话,就算加上 34,最多就是 3 个数,不可能到达 k = 4 的个数,所以就是无效遍历!所以,可以剪枝的地方就在递归中每一层的 for 循环所选择的起始位置。

​ 也就是说,如果 for 循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。

​ 注意代码中 i,就是 for 循环里选择的起始位置。

for(int i = cur; i <= n; ++i)

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size()
  2. 还所需的元素个数为: k - path.size()
  3. 因为 列表中剩余元素(n - i + 1)≥ 还所需的元素个数(k - path.size() 才有意义要遍历下去!
  4. 所以最后得到:i ≤ n - (k - path.size()) + 1

​ 所以优化之后的 for 循环是:

for(int i = cur; i <= n-(k-path.size())+1; ++i) // 剪枝优化

​ 优化后整体代码如下:

class Solution {
    vector<vector<int>> ret; // 存放结果集
    vector<int> path;        // 存放当前路径中的元素
public:
    vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);
        return ret;
    }

    void dfs(int n, int k, int cur)
    {
        // 递归函数出口
        if(path.size() == k)
        {
            ret.push_back(path);
            return;
        }

        for(int i = cur; i <= n-(k-path.size())+1; ++i) // 剪枝优化
        {
            // 处理当前节点
            path.push_back(i);

            // 递归处理当前节点后面的路径
            dfs(n, k, i + 1);

            // 回溯处理
            path.pop_back();
        }
    }
};

在这里插入图片描述


http://www.niftyadmin.cn/n/5839092.html

相关文章

Flink2支持提交StreamGraph到Flink集群

最近研究Flink源码的时候&#xff0c;发现Flink已经支持提交StreamGraph到集群了&#xff0c;替换掉了原来的提交JobGraph。 新增ExecutionPlan接口&#xff0c;将JobGraph和StreamGraph作为实现。 Flink集群Dispatcher也进行了修改&#xff0c;从JobGraph改成了接口Executio…

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算 一、 插入音频及音频设置 1.插入音频&#xff1a;点击菜单栏插入-音频-选择PC上的音频&#xff08;已存在的音频&#xff09;或者录制音频&#xff08;现场录制…

Koa 基础篇(二)—— 路由与中间件

let app new Koa() router.get(“/”,async ctx > { ctx.body “hello koa router” }) app.use(router.routes()) app.use(router.allowedMethods()) app.listen(3000) 运行项目&#xff0c;在浏览器访问本地3000端口&#xff0c;在页面上就会看到输出的语句。这就…

deepseek核心技术:MLA架构-多头潜在注意力

deepseek核心技术:MLA架构-多头潜在注意力 MLA架构即Multi-Head Latent Attention(多头潜在注意力)架构,是一种优化后的注意力机制。以下是对其及相关示例的具体介绍: 工作原理 输入嵌入:将输入序列中的每个元素转换为向量表示,即嵌入向量。例如在处理文本时,将文本中…

Verilog边沿检测

edge_check.v module edge_check(input clk,input in,output neg_edge,output pos_edge);reg r11d0;reg r21d0;assign neg_edge(~r1)&r2;assign pos_edger1&(~r2);always(posedge clk)beginr1<in;r2<r1;endendmodule tb.v timescale 1ns/1nsmodule tb; //被测…

数据库性能优化(sql优化)_SQL执行计划03_yxy

数据库性能优化_SQL执行计划详解03 1 排序、聚集类操作符1.1 SORT 排序1.2 聚集AAGR 简单聚集FAGR 快速聚集HAGR HASH分组聚集SAGR 流分组聚集1.3 排序、聚集类操作符总结2 执行计划读取技巧1 排序、聚集类操作符 1.1 SORT 排序 它的主要功能是按照指定的列(或列组合)以及排…

Vue.js组件开发-实现全屏焦点图片带图标导航按钮控制图片滑动切换

使用 Vue 实现全屏焦点图片带图标导航按钮控制图片滑动切换 步骤 创建 Vue 项目&#xff1a;可以使用 Vue CLI 快速创建一个新的 Vue 项目。设计组件结构&#xff1a;创建一个包含图片展示区域和导航按钮的组件。实现图片滑动切换逻辑&#xff1a;通过点击导航按钮切换图片。…

2025-1-28-sklearn学习(47) (48) 万家灯火亮年至,一声烟花开新来。

文章目录 sklearn学习(47) & (48)sklearn学习(47) 把它们放在一起47.1 模型管道化47.2 用特征面进行人脸识别47.3 开放性问题: 股票市场结构 sklearn学习(48) 寻求帮助48.1 项目邮件列表48.2 机器学习从业者的 Q&A 社区 sklearn学习(47) & (48) 文章参考网站&…