【算法总结】——组合型回溯

news2024/11/16 0:40:27

文章目录

  • 组合型回溯
    • 例题1——组合
      • 从输入考虑模板
      • 从答案考虑模板
    • 例题2——括号生成
      • 解法一
      • 解法二
  • 剪枝
  • 分析回溯时间复杂度的通用方法

组合型回溯

组合型和子集型之间的差异在哪里呢?
在这里插入图片描述

相比子集问题,组合问题是可以做一些额外的优化的(因为只需要得到某些特定的子集)。


同样是 【选和不选】 以及 【枚举选哪个】 这两种思路。
(具体用哪种思路根据题目特点来做,哪种更好些就选哪种写法)

例题1——组合

https://leetcode.cn/problems/combinations/
在这里插入图片描述

从输入考虑模板

依然是考虑每个数字选或不选,与子集型回溯的差异在于,结果的元素数量必须是k。

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

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

    public void dfs(int i, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        if (i > n) return;
        dfs(i + 1, n, k);
        t.add(i);
        dfs(i + 1, n, k - 1);
        t.remove(t.size() - 1);
    }
}

从答案考虑模板

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

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

    public void dfs(int startIndex, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        for (int i = startIndex; i <= n; ++i) {
            t.add(i);
            dfs(i + 1, n, k - 1);
            t.remove(t.size() - 1);
        }
    }
}

例题2——括号生成

解法一

选左括号还是右括号

class Solution {
    List<String> ans = new ArrayList();
    StringBuilder t = new StringBuilder();

    public List<String> generateParenthesis(int n) {
        dfs(n, n);
        return ans;
    }

    public void dfs(int n1, int n2) {
        if (n1 == 0 && n2 == 0) {
            ans.add(t.toString());
            return;
        }
        if (n1 > 0) {   // 左括号
            t.append('(');
            dfs(n1 - 1, n2);
            t.deleteCharAt(t.length() - 1);
        }
        if (n2 > n1) {  // 右括号
            t.append(')');
            dfs(n1, n2 - 1);
            t.deleteCharAt(t.length() - 1);
        }
    }
}

其实回溯里的 if 就相当于是在做剪枝了,所谓的剪枝就是提前过滤掉不合理的答案。

解法二

枚举左括号出现的位置

class Solution {
    List<String> ans = new ArrayList();
    List<Integer> pos = new LinkedList();   // 存储左括号的位置

    public List<String> generateParenthesis(int n) {
        dfs(0, 0, n);
        return ans;
    }

    public void dfs(int startIndex, int cnt, int n) {
        if (cnt == n) {
            char[] chs = new char[2 * n];
            Arrays.fill(chs, ')');
            for (int idx: pos) chs[idx] = '(';
            ans.add(new String(chs));
            return;
        }
        for (int i = startIndex; i <= 2 * cnt; ++i) {
            pos.add(i);
            dfs(i + 1, cnt + 1, n);
            pos.remove(pos.size() - 1);
        }
    }
}

这里 for 循环中的 i <= 2 * cnt 相当于剪枝。

剪枝

这里以解法一为例介绍剪枝操作。
由于答案中必须有 k 个元素,因此如果选到 i 的时候,还有 n - i + 1 个元素可供选择,因此如果可供选择的数字已经少于还需要选择的元素,那么就可以不再继续往下尝试了。

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

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

    public void dfs(int i, int n, int k) {
        if (n - i + 1 < k) return;	// 剪枝
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        dfs(i + 1, n, k);
        t.add(i);
        dfs(i + 1, n, k - 1);
        t.remove(t.size() - 1);
    }
}

对于模板二的剪枝方式为:

class Solution {
    List<List<Integer>> ans = new ArrayList();
    List<Integer> t = new ArrayList();

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

    public void dfs(int startIndex, int n, int k) {
        if (k == 0) {
            ans.add(new ArrayList(t));
            return;
        }
        for (int i = startIndex; i <= n - k + 1; ++i) {		// 剪枝 i <= n - k + 1
            t.add(i);
            dfs(i + 1, n, k - 1);
            t.remove(t.size() - 1);
        }
    }
}

分析回溯时间复杂度的通用方法

时间复杂度是:**叶子的个数** 乘上 **从根到叶子的路径长度**
对于这道题目(例题一)来说,就是 k * C(n, k)

在这里插入图片描述

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

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

相关文章

Linux模块文件编译到内核与独立编译成.ko文件的方法

很多粉丝在群里提问&#xff0c;如何把一个模块文件编译到内核中或者独立变异成ko文件。本文给大家详解讲解。 1. 内核目录 Linux内核源代码非常庞大&#xff0c;随着版本的发展不断增加。它使用目录树结构&#xff0c;并且使用Makefile组织配置、编译。 初次接触Linux内核&…

Visual Studio 2022写Windows程序造成CPU占用率过高故障排除

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天针对Visual Studio 2022写Windows程序造成CPU占用率过高故障进行排除。 下面是一个标准的Windows程序&#xff0c;也可以说是经典程序了&#xff0c;但是这个程序一运行&#xff0c;WinMain.exe的CPU占用…

Android 13(T) - binder阅读(2)- ServiceManager的启动与获取

1 ServiceManager的启动 1.1 服务的启动与注册 上一篇笔记中有说到&#xff0c;ServiceManager是一个特殊的binder service&#xff0c;所以它和普通的service一样需要打开binder驱动&#xff0c;在驱动中创建一个属于ServiceManager进程的binder_proc。 int main(int argc,…

django中发送get post请求并获得数据

django中发送get post请求并获得数据 项目结构如下注册路由 urls.py在处理函数中处理请求 views.py进行 get的请求01浏览器 get请求传参数02服务器django get参数解析获取01浏览器 post的发送浏览器get 请求 获取页面返回的 form 发送post请求 带参数 02服务器django的post请求…

【Unity3D】平面光罩特效

1 前言 屏幕深度和法线纹理简介中对深度和法线纹理的来源、使用及推导过程进行了讲解&#xff0c;激光雷达特效中讲述了一种重构屏幕像素点世界坐标的方法&#xff0c;本文将沿用激光雷达特效中重构像素点世界坐标的方法&#xff0c;实现平面光罩特效。 假设平面光罩的高度为 s…

SpringCloud Alibaba入门7之引入服务网关Gateway

我们需要在客户端和服务端之间加一个统一的入口&#xff0c;来作为请求的统一接入&#xff0c;而在微服务的体系中&#xff0c;承担这个角色的就是网关。我们只需要将网关的机器IP配置到DNS,或者接入负载&#xff0c;那么客户端的服务最终通过我们的网关&#xff0c;再转发到对…

GEE:欧几里得距离——计算目标图像中每个像素到目标像素的距离

作者:CSDN @ _养乐多_ 利用欧几里得距离计算目标图像中每个像素到目标像素的距离,以量化像素与目标的接近程度。 结果如下图所示, 文章目录 一、欧几里得距离简介二、代码一、欧几里得距离简介 欧几里得距离(Euclidean distance)是在数学中常用的一种距离度量方式,用于…

Android PMS APP安装流程

仓库网址&#xff1a;http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java 一、PMS安装APP流程图 二、文件复制 PMS处理安装HandlerParams安装参数流程图 PackageManagerService.java#installStage…

职场求生记|唐朝打工人如何绝地求生

&#x1f4da;书名&#xff1a;《长安的荔枝》 ✏️作者&#xff1a;马伯庸 作为“见微”系列神作&#xff0c;其在微信读书总榜的第一名位置持续一段时间了&#xff0c;其讲述的内容和每个人都息息相关&#xff0c;更是能引起职场人的无限共鸣&#xff0c;值得深思。 ⭐故事…

使用networkx查看某一个节点的一阶/二阶/三阶邻居

文章目录 前言手动高级 前言 一般情况下&#xff0c;貌似这些图之类的包&#xff0c;只提供查询一个节点的一阶邻居&#xff0c;但是有的时候我们需要二阶甚至三阶&#xff0c;那么该如何做呢&#xff1f; 注意一下&#xff0c;本文的方法仅可以针对二阶或者三阶&#xff0c;…

一分钟 帮你搞懂什么是柔性数组!

文章目录 什么是柔性数组&#xff1f;柔性数组的特点柔性数组的使用模拟实现柔性数组的功能柔性数组的优势 什么是柔性数组&#xff1f; 柔性数组这个概念相信大多数人博友都没有听说过&#xff0c;但是它确实存在。 在C99中&#xff0c;结构&#xff08;结构体&#xff09;的…

【雕爷学编程】Arduino动手做(121)---夏普粉尘传感器模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

EMC学习笔记(十一)过孔

过孔 1.过孔模型1.1 过孔的数学模型1.2 对过孔模块的影响因素 2.过孔对信号传导与辐射发射影响2.2 过孔对阻抗控制的影响2.2 过孔数量对信号质量的影响 1.过孔模型 从过去设计的一些PCB板效果来看&#xff0c;过孔对于低频&#xff0c;低速信号的影响是很小的&#xff0c;但是…

Android 窗口实现原理

一、基本概念 1、窗口显示架构图 多窗口的核心原理其实就是分栈和设置栈边界2、Android的窗口分类 Android应用程序窗口,这个是最常见的&#xff08;拥有自己的WindowToken)譬如&#xff1a;Activity与Dialog Android应用程序子窗口&#xff08;必须依附到其他非子窗口才能存…

深度学习-第T11周——优化器对比实验

深度学习-第T11周——优化器对比实验 深度学习-第T11周——优化器对比实验一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据 四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签 2、数据可视化3、检查数据4、配置数据集…

6月份读书学习好文记录

看看CHATGPT在最近几个月的发展趋势 https://blog.csdn.net/csdnnews/article/details/130878125?spm1000.2115.3001.5927 这是属于 AI 开发者的好时代&#xff0c;有什么理由不多去做一些尝试呢。 北大教授陈钟谈 AI 未来&#xff1a;逼近 AGI、融进元宇宙&#xff0c;开源…

06-浏览器渲染原理

什么是渲染&#xff1f; render&#xff0c;HTML字符串 --渲染--> 像素信息 URL地址是一个字符串&#xff0c;HTML、css、js都在里面 可以把渲染想象成一个函数&#xff0c;上代码&#xff1a; function render (html) {/* 第一行第二行*/return pixels; } 渲染时间点 …

【深入浅出 Spring Security(十二)】使用第三方(Github)授权登录

使用第三方&#xff08;Github&#xff09;授权登录 一、OAuth2 简单概述二、OAuth2 四种授权模式之授权码模式三、Github 授权登录准备工作创建 Spring Boot 项目Vue 测试代码测试效果 &#xff08;Github授权登录的具体操作在目录第三“章”&#xff09; 一、OAuth2 简单概述…

Spring Boot 优雅集成 Spring Security 5.7(安全框架)与 JWT(双令牌机制)

Spring Boot 集成 Spring Security &#xff08;安全框架&#xff09; 本章节将介绍 Spring Boot 集成 Spring Security 5.7&#xff08;安全框架&#xff09;。 &#x1f916; Spring Boot 2.x 实践案例&#xff08;代码仓库&#xff09; 介绍 Spring Security 是一个能够为基…

【CSDN创作纪念日】——博客小梦的“256”鸭~

博客小梦的创作纪念日&#x1f60e; 前言&#x1f64c;与CSDN的相遇浑水摸鱼的日常CSDN上的小小收获收获了 一群热爱编程&#xff0c;热爱创作的CSDN挚友创作上的小荣誉 憧憬未来 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭…