LeetCode-77. 组合

news2024/11/18 11:24:17

目录

    • 回溯法
    • 剪枝优化

题目来源
77. 组合

回溯法

  • 1.递归函数的返回值以及参数

在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。

    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

其实不定义这两个全局变量也是可以的,把这两个变量放进递归函数的参数里,但函数里参数太多影响可读性,所以我定义全局变量了。
函数里一定有两个参数,既然是集合n里面取k个数,那么n和k是两个int型的参数。
然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,…,n] )。
从下图中红线部分可以看出,在集合[1,2,3,4]取1之后,下一层递归,就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢,靠的就是startIndex。
在这里插入图片描述
所以需要startIndex来记录下一层递归,搜索的起始位置。
那么整体代码如下:

    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    void combineHelper(int n, int k, int startIndex)
  • 2.回溯函数终止条件

什么时候到达所谓的叶子节点了呢?
path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径。
如图红色部分:
在这里插入图片描述
此时用result二维数组,把path保存起来,并终止本层递归。
所以终止条件代码如下:

        //终止条件
        if(path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
  • 3.单层搜索的过程
    回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。
    在这里插入图片描述
    如此我们才遍历完图中的这棵树。
    for循环每次从startIndex开始遍历,然后用path保存取到的节点i。
    代码如下:
        for(int i = startIndex;i <= n;i++){  // 控制树的横向遍历
            path.add(i);   // 处理节点 
            combineHelper(n, k, i+1);  // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
            path.removeLast();  // 回溯,撤销处理的节点
        }

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

class Solution {

    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        if(path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i <= n;i++){  // 控制树的横向遍历
            path.add(i);   // 处理节点 
            combineHelper(n, k, i+1);  // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
            path.removeLast();  // 回溯,撤销处理的节点
        }
    }
}

在这里插入图片描述

剪枝优化

在遍历的过程中有如下代码:

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

这个遍历的范围是可以剪枝优化的,怎么优化呢?
来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。
这么说有点抽象,如图所示:
在这里插入图片描述
图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。

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

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

接下来看一下优化过程如下:
已经选择的元素个数:path.size();
所需需要的元素个数为: k - path.size();
列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历
为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。
从2开始搜索都是合理的,可以是组合[2, 3, 4]。
优化后整体代码如下:

class Solution {

    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        if(path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i <= n - (k - path.size()) + 1;i++){  // 控制树的横向遍历
            path.add(i);   // 处理节点 
            combineHelper(n, k, i+1);  // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
            path.removeLast();  // 回溯,撤销处理的节点
        }
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349818.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据分析就要选择这款免费报表工具

对于一家企业来说&#xff0c;在日常运营的过程中本身就会产出很多的数据&#xff0c;那么这些数据本身就应该形成报表。可是如果只是选择手工的一种操作&#xff0c;确实需要浪费大量的人力物力。伴随着科技进入到快速发展的阶段&#xff0c;市面上更是出现了很多报表工具可以…

九龙证券|可转债一级市场回暖 14家上市公司可转债发行集中获批

可转债商场悄然升温。春节假期后&#xff0c;可转债新券上市体现普遍不错&#xff0c;多只个券首日涨幅打破30%&#xff0c;更有3个买卖日就实现翻倍的案例。一起&#xff0c;本周初可转债打新户数本年以来也首度站上1000万户大关。 因为新券盈余效应明显&#xff0c;可转债一级…

window10安装MySQL数据库

准备好软件MySql的下载参考&#xff1a;(1137条消息) mysql下载与安装过程_weixin_40396510的博客-CSDN博客_mysql数据库下载安装(1137条消息) 安装MySQL的常见问题_二木成林的博客-CSDN博客_sc不是内部或外部命令,也不是可运行的程序解压要C盘&#xff08;自定义&#xff0c;本…

Ubuntu——扩展磁盘空间,可视化软件简单很多

目录 1. 剩余空间查看 2. 虚拟机先分配 3. 安装与使用 gparted 1. 剩余空间查看 2. 虚拟机先分配 关闭虚拟机&#xff0c;打开虚拟机&#xff0c;但不启动&#xff0c;编辑虚拟机设置——点击硬盘- 拓展 设置扩展大小&#xff0c;确定。 但是此时我们的分区和文件 并没有扩容…

阅读笔记3——空洞卷积

空洞卷积 1. 背景 空洞卷积&#xff08;Dilated Convolution&#xff09;最初是为解决图像分割的问题而提出的。常见的图像分割算法通常使用池化层来增大感受野&#xff0c;同时也缩小了特征图尺寸&#xff0c;然后再利用上采样还原图像尺寸。特征图先缩小再放大的过程造成了精…

HummerRisk V0.9.1:操作审计增加百度云,增加主机检测规则及多处优化

HummerRisk V0.9.0发布&#xff1a;增加RBAC 资源拓扑图&#xff0c;首页新增检查的统计数据&#xff0c;云检测、漏洞、主机等模块增加规则&#xff0c;对象存储增加京东云&#xff0c;操作审计增加金山云&#xff0c;镜像仓库新增设置别名。 感谢社区中小伙伴们的反馈&#…

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型

概述 对于surfaceflinger大多数人都知道它的功能是做图形合成的&#xff0c;用英语表示就是指composite。其大致框图如下: 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程surfaceflinger进程中的composition engine与HW…

如何赋能智能运维,迈出数字化黑匣子第一步?

在当下大数据时代&#xff0c;诸多行业专家为企业智能运维绘出美好蓝图。在该蓝图中&#xff0c;互联网、云计算、大数据分析联合发力&#xff0c;企业在能“攻”能“守”中快速、可持续发展。何为“攻”&#xff1f;对支撑企业产品研发、生产、管理、营销等各业务链条的IT基础…

指针数组和数组指针、字符指针

文章目录指针字符指针实例一实例二指针数组实例一实例二实例三数组指针实例一实例二实例三-看书呗指针 1.指针是个变量&#xff0c;用来存放地址&#xff0c;地址将唯一标识一块内存空间 内存编号地址指针 2.指针的大小是固定的&#xff0c;32位平台是4个字节&#xff0c;64位…

【QT】UDP通信QUdpSocket(单播、广播、组播)

目录1. UDP通信概述2. UDP消息传送的三种模式3. QUdpSocket类的接口函数4. UDP单播和广播代码示例4.1 测试说明4.2 MainWindow.h4.3 MainWindow.cpp4.4 界面展示5. UDP组播代码示例5.1 组播的特性5.2 MainWindow.h5.3 MainWindow.cpp5.4 界面展示1. UDP通信概述 UDP是无连接、…

驱动程序开发:基于EC20 4G模块自动拨号联网的两种方式(GobiNet工具拨号和PPP工具拨号)

目录一、EC20 4G模块简介二、根据移远官方文档修改EC20 4G模组驱动  1、因为EC20 4G模组min-pice接口其实就是usb接口&#xff0c;因此需要修改Linux内核源码drivers/usb/serial/option.c文件&#xff0c;如下图&#xff1a;  2、根据USB协议的要求&#xff0c;需要在drive…

从FPGA说起的深度学习(三)

这是新的系列教程&#xff0c;在本教程中&#xff0c;我们将介绍使用 FPGA 实现深度学习的技术&#xff0c;深度学习是近年来人工智能领域的热门话题。在本教程中&#xff0c;旨在加深对深度学习和 FPGA 的理解。用 C/C 编写深度学习推理代码高级综合 (HLS) 将 C/C 代码转换为硬…

linux下安装elasticsearch步骤

linux下安装elasticsearch步骤&#xff1a; 1、Elasticsearch的下载&#xff08;选择7.8.0&#xff09; 1.1、elasticsearch国内社区&#xff1a; https://elasticsearch.cn/1.2、elasticsearch官网地址&#xff1a; https://www.elastic.co/cn/elasticsearch/1.3、elastic…

从零实现Web服务器(二): 线程池以及线程池的作用,Get和Post的区别,项目中如何编写数据库连接池,定时器优化非活跃连接

文章目录一、线程池以及线程池的作用二、手写线程池三、Get和Post的区别四、如何编写数据库连接池五、定时器优化非活跃连接5.1. 基于排序链表实现。5.2. 基于小根堆实现。5.3. 基于红黑树实现。5.4. 基于时间轮实现。5.4.1 单时间轮实现5.4.2 多时间轮实现一、线程池以及线程池…

JavaScript 入门教程||javascript 简介||JavaScript 用法

javascript 简介JavaScript 是互联网上最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。JavaScript 是脚本语言JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代…

C++之lambda函数(匿名函数)

lambda函数简介lambda函数是C11标准新增的语法&#xff0c;也称为lambda表达式或匿名函数。lambda函数的特点是&#xff1a;距离近、简洁、高效和功能强大。优点声明式编程风格&#xff1a;就地匿名定义目标函数或函数对象&#xff0c;有更好的可读性和可维护性。简洁&#xff…

webspider_20230216

下载网页源代码 分析下规则 抓取咱们需要的信息 仅限于合法的行为&#xff0c;仅仅提供自动化&#xff0c;省得手工去获取图片、手机、邮箱等信息

Spring Cloud是什么?怎么理解Spring Cloud?

简介Spring Cloud项目的官方网址&#xff1a;https://projects.spring.io/spring-cloud/ Spring Cloud 并不是一个项目&#xff0c;而是一组项目的集合。在 Spring Cloud中包含了很多的子项目&#xff0c;每一个子项目都是一种微服务开发过程中遇到的问题的一种解决方案。它利…

机场航站楼告别人工巡检,提高空间安全性

机场是现代化民航基础设施体系&#xff0c;加快数字化转型、建设更高水平的智慧城市已成为城市竞争的新赛道。图扑软件基于 HT 引擎为国内民航数字化转型支撑可视化服务提供有力的一环&#xff0c;融合物联网解决方案&#xff0c;打造适配场景的智慧机场三维可视化解决方案&…

面试题整理

面试题整理 一、Java基础 1、Java 语言有哪些特点 简单易学&#xff1b; 面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff1b; 平台无关性&#xff08; Java 虚拟机实现平台无关性&#xff09;&#xff1b; 支持多线程&#xff08; C 语言…