1.回溯算法.基础

1.回溯算法

  • 基础知识
  • 题目
    • 1.组合
    • 2.组合-优化
    • 3.组合总和|||
    • 4.电话号码和字母组合
    • 5.组合总和
    • 6.组合总和II
    • 7.分割回文串
    • 8.复原IP地址

基础知识

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。回溯是递归的副产品,只要有递归就会有回溯
因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

回溯算法能解决的问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

理解回溯法:
回溯法解决的问题都可以抽象为树形结构所有回溯法的问题都可以抽象为树形结构。因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

回溯法模板

  • 回溯函数模板返回值以及函数,习惯是函数起名字为backtracking,函数返回值一般为void
  • 回溯函数终止条件,一般来说是搜到叶子节点了
  • 回溯搜索的遍历过程
    在这里插入图片描述
    for循环就是遍历集合的区间,一个节点有多少个子节点,for循环就会执行多少次。
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}

这份模板很重要,后面做回溯法的题目都基本是按照该模板解决。
回溯和递归是相辅相成的,回溯法的效率,回溯法其实就是暴力查找,并不是什么高效的算法。最后我们讲到回溯法解决的问题都可以抽象为树形结构(N叉树),。并给出了回溯法的模板。


题目

1.组合

(题目链接)
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
这题是回溯法的经典题目,直接解法是通过k个for循环,不考虑顺序的情况下完成组合问题。但当k值较大时,暴力搜索的代码太过冗长。因此可以使用递归法,每次递归中嵌套一个for循环,那么递归就可以用于解决过曾的嵌套系统问题。例如:
在这里插入图片描述

    std::vector<std::vector<int>> res;std::vector<int> path;void backtracking(int n, int k, int startindex){if(path.size()==k){res.push_back(path);return;}for(int i=startindex; i<=n; i++){path.push_back(i);backtracking(n, k, i+1);path.pop_back();}}vector<vector<int>> combine(int n, int k) {res.clear();path.clear();backtracking(n,k,1);return res;}

2.组合-优化

题目1中的回溯法是可以剪枝优化的,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置,以此避免一些没有必要的循环。在组合问题中如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
1.已经选择的元素个数:path.size();2.所需需要的元素个数为: k - path.size();3.列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size());4.在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,这里+1是因为for循环取的索引值是左闭区间。
修改之后的for循环条件如下

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

3.组合总和|||

(题目链接)
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。说明:所有数字都是正整数;解集不能包含重复的组合。
这题相当于在组合问题的基础上多了一个限制,就是找到和为n的k个数的组合,而整个集合已经是固定的1~9。

    std::vector<std::vector<int>> res;std::vector<int> path;void backtracking(int tarSum, int k, int sum, int startindex){if(path.size()==k && sum == tarSum){res.push_back(path);}for(int i=startindex; i<=9 - (k - path.size()) + 1; i++){sum += i;path.push_back(i);if(sum > tarSum){sum -= i;path.pop_back();return;}backtracking(tarSum, k, sum, i+1);sum -= i;path.pop_back();}}    vector<vector<int>> combinationSum3(int k, int n) {res.clear();path.clear();backtracking(n, k, 0, 1);return res;}

4.电话号码和字母组合

(题目链接)
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合
在这里插入图片描述
例如:输入:“23”;输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]。(尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。)
根据输入的数字,可以分为一层递归,所以本题是要解决如下三个问题:

  • 数字和字母的映射
  • 输入数字还包含异常的情况,例如1*#的情况

解决第一个问题,需要建立一个0~9队形string charMap[10]的字符串数组结构;
确定回溯函数参数:需要字符串收集叶子节点的结果,然后用一个字符串数组res保存,这两个变量设置为全局。除此之外,局部变量为接收题目中给的string digits,以及int index-记录digits第几个数字。
返回条件,当index==digits.size()

    const string letterMap[10] = {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9};std::vector<std::string> res;std::string s;void backtracking(const string& digits, int index){if(index==digits.size()){res.push_back(s);return;}int digit = digits[index]-'0';string letters = letterMap[digit];for(int i=0; i<letters.size(); i++){s.push_back(letters[i]);backtracking(digits, index+1);s.pop_back();}}vector<string> letterCombinations(string digits) {res.clear();s.clear();if(digits.size()==0) return res;backtracking(digits, 0);return res;}

此时不需要在backtracking中设置startindex,因为是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex。但如果是一个集合来求组合的话,就需要startIndex
时间复杂度: O(3^ m*4^ n , 空间复杂度: O(3^ m * 4^n),其中 m 是对应三个字母的数字个数,n 是对应四个字母的数字个数。


5.组合总和

(题目链接)
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。说明:candidates 中的数字可以无限制重复被选取;所有数字(包括 target)都是正整数;解集不能包含重复的组合。例如输入:candidates = [2,3,5], target = 8;所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]。
与之前组合问题不同的是,这题没有数量的限制,可以无限重复,但总和有限制,所以递归的次数也是有限制的。
递归函数参数:两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果;题目传入的集合candidates, 和目标值target。使用int sum来统计path里的总和,同时需要startindex来控制for循环的起始位置
终止条件:当sum大于等于tarsum时,递归终止。
当然这题也是可以作剪枝优化处理的,就是在for循环条件-如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历

    std::vector<std::vector<int>> res;std::vector<int> path;void backtracking(std::vector<int>& candidates, int target, int sum, int startindex){if(sum==target){res.push_back(path);return;}for(int i=startindex; i<candidates.size() && sum+candidates[i]<=target; i++){sum += candidates[i];path.push_back(candidates[i]);// 此处传入i,而不是i+1,是因为元素可以重复backtracking(candidates, target, sum, i);sum -= candidates[i];path.pop_back();}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {res.clear();path.clear();std::sort(candidates.begin(), candidates.end());// 排序有利于加速剪枝backtracking(candidates, target, 0, 0);return res;}

6.组合总和II

(题目链接)
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。说明:candidates 中的每个数字在每个组合中只能使用一次,所有数字(包括目标数)都是正整数。解集不能包含重复的组合
这题与第4题的区别是,candidates中的数字只能用一次,而且该数组可能存在重复的元素。正确理解重复的组合:“使用过”在组合问题(树形结构)上有两个维度:同一个树枝上使用,同一个数层上使用。结合题目我们要去重的是同一树层上”使用过“的元素,同一树枝上都是一个组合里的元素,不用去重
在这里插入图片描述
因此需要还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

    std::vector<std::vector<int>> res;std::vector<int> path;void backtracking(std::vector<int>& candidates, int target, int sum, int startindex, std::vector<bool> used){if(sum==target){res.push_back(path);return;}for(int i=startindex; i<candidates.size() && sum+candidates[i]<=target;i++ ){// 这一步的基础是sort,因此相等的元素会排列在一起if(i>0 && candidates[i]== candidates[i-1] && used[i-1]==false) continue;sum += candidates[i];path.push_back(candidates[i]);used[i] = true;backtracking(candidates, target, sum, i+1, used);sum -= candidates[i];path.pop_back();used[i] = false;}}vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {std::vector<bool> used(candidates.size(), false);res.clear();path.clear();std::sort(candidates.begin(), candidates.end());backtracking(candidates, target, 0, 0, used);return res;}

7.分割回文串

(题目链接)
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。例如示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]

切割问题其实类似组合问题:切割问题最后可以抽象为一颗树的结构

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个…。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段…。
    在这里插入图片描述
    递归函数参数:全局变量数组path存放切割后回文的子串,二维数组result存放结果集。参数还需要startIndex,因为切割过的地方,不能重复切割。递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。另外切割的是以startindex的左闭区间,因此当startindex0时会出现空切==的情况出现。
    终止条件:切割线切到了字符串最后面,说明找到了一种切割方法。
    如何判断回文字符串:使用双指针法,左端,右端向中间靠拢,令char[i]==char[j]就是回文字符串。
    std::vector<std::vector<std::string>> res;std::vector<std::string> path;void backtracking(std::string& s, int startindex){if(startindex>=s.size()){res.push_back(path);return;}for(int i=startindex; i<s.size() ; i++){if(ispalindrome(s, startindex, i)){std::string str = s.substr(startindex, i-startindex+1);path.push_back(str);                }else continue;backtracking(s, i+1);path.pop_back();}}bool ispalindrome(const std::string& s, int start, int end){for(int i=start, j=end; i<j; i++, j--){if(s[i]!=s[j]) return false;}return true;}vector<vector<string>> partition(string s) {res.clear();path.clear();backtracking(s, 0);return res;}

8.复原IP地址

(题目链接)
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔
递归参数:这题类似分割回文串,因为需要添加逗号,需要设置变量pointNum记录;
递归终止条件:当pointNum==3时,验证一下第四段是否合法,如果合法就加入集合里;
单层搜索的逻辑:在循环遍历里截取子串,不过需要判断该子串是否合法,如果合法,则添加.表示已经分割;如果不合法就结束本层循环。
判断子串是否合法:段位以0为开头的数字不合法;段位里有非整数字符;段位大于255

    std::vector<std::string> res;void backtracking(std::string& s, int startindex, int pointnum){if(pointnum==3){if(isvalid(s, startindex, s.size()-1)) res.push_back(s);return;}for(int i=startindex; i<s.size(); i++){if(isvalid(s, startindex, i)){s.insert(s.begin()+i+1, '.');pointnum++;backtracking(s, i+2, pointnum);pointnum--; // 回溯s.erase(s.begin()+i+1);}else break; // 首段不合法,直接结束本层该分支的循环}}bool isvalid(const string& s, int start, int end){// 异常if(start>end) return false;// 开头为0if(s[start]=='0' && start!=end) return false;int num=0;for(int i=start; i<=end; i++){// 不在0~9范围内的字符if(s[i]>'9' || s[i]<'0') return false;num = num*10 + (s[i]-'0');// 超出255范围的字符if(num>255) return false;}return true;}vector<string> restoreIpAddresses(string s) {res.clear();if(s.size()<4 || s.size()>12) return res;backtracking(s, 0, 0);return res;}

时间复杂度: O(3^4),IP地址最多包含4个数字,每个数字最多有3种可能的分割方式,则搜索树的最大深度为4,每个节点最多有3个子节点;空间复杂度: O(n)

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

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

相关文章

Excel 宏录制与VBA编程 —— 11、工作表及工作簿操作(附:Worksheets与Sheets区别)

代码1 - Worksheets与Sheets区别 Worksheets表示普通工作表;Sheets即可表示普通工作表也可表示图标工作表。 下面模块中代码结果是一样的,大家理解时可结合上面区别说明进行了解 Sub Test()Worksheets("Sheet1").Range("A1").Value 100Sheets("Sheet…

BioCLIP:物种图像的基础视觉模型

从无人机到个人手机&#xff0c;各种相机收集的自然世界图像是越来越丰富的生物信息来源。从图像中提取生物相关信息用于科学的计算方法和工具激增&#xff0c;尤其是计算机视觉。然而&#xff0c;其中大多数都是为特定任务设计的&#xff0c;不容易适应或扩展到新的问题、环境…

【AI大模型】Transformers大模型库(十二):Evaluate模型评估

目录 一、引言 二、Evaluate模型评估 2.1 概述 2.2 使用方法 2.2.1 步骤1: 导入必要的库 2.2.2 步骤2: 加载模型和分词器 2.2.3 步骤3: 准备数据集 2.2.4 步骤4: 数据预处理 2.2.5 步骤5: 创建训练和评估数据集 2.2.6 步骤6: 设置训练参数并创建Trainer 2.2.7 步…

基于Flask开发的前后端交互项目(可用于期末大作业) MySQL数据库 文件上传 Spider爬虫 Echarts可视化展示 JS动态

项目描述&#xff1a; 开发一个基于Flask框架开发的前后端交互项目&#xff0c;项目内容为 东京奥运会 。对各个需要填写的字段做了数据验证&#xff0c;非法信息会被JS拦截提醒不合法&#xff1b;还对未登录就访问做了拦截&#xff0c;阻止未登录就访问。 前端&#xff1a;HT…

idea 开发工具properties文件中的中文不显示

用idea打开一个项目&#xff0c;配置文件propertise中的中文都不展示&#xff0c;如图&#xff1a; 可修改idea配置让中文显示&#xff1a; 勾选箭头指向的框即可&#xff0c;点击应用保存&#xff0c;重新打开配置文件&#xff0c;显示正常

Java开发环境配置

一、JDK 下载JDK&#xff1a;Java Downloads | Oracle 配置环境变量&#xff1a;09、Java入门&#xff1a;Path、JAVA_HOME环境变量配置_哔哩哔哩_bilibili 二、IDEA 下载IDEA&#xff1a; Download IntelliJ IDEA – The Leading Java and Kotlin IDE (jetbrains.com) 建…

HotSpot 垃圾收集器

文章目录 前言HotSpot 垃圾收集器1. 查看jdk默认垃圾收集器命令2. 查看当前服务使用的是哪个垃圾收集器:3. 常用的垃圾收集器3.1. 并行垃圾收集器&#xff08;Parallel Garbage Collector&#xff09;3.2. CMS 垃圾收集器&#xff08;Concurrent Mark-Sweep Garbage Collector&…

情感分析方法与实践

第1关&#xff1a;情感分析的基本方法 情感分析简介 情感分析&#xff0c;又称意见挖掘、倾向性分析等。简单而言&#xff0c;是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。在日常生活中&#xff0c;情感分析的应用非常普遍&#xff0c;下面列举几种常见的…

Gradle学习-3 Gradle插件

1、Gredle插件是什么 Gradle插件是用于扩展和增强Gradle构建系统的功能模块通过插件&#xff0c;Gradle可以执行各种构建任务&#xff0c;如编译代码、打包应用、运行测试等 Gradle插件主要分为&#xff1a;二进制插件、脚本插件 二进制插件二进制插件是预编译的、可以复用的…

web学习笔记(七十二)

目录 1.vue2通过$parent实现组件传值——父传子 2.vue2 通过$children实现组件传值——子传父 3. provide和inject传值&#xff08;依赖注入&#xff09; 4.vue2如何操作dom 5.vue2如何拿到最新的dom 6.filters过滤器 7.vue2的生命周期 8.vuex的用法 1.vue2通过$parent…

大数据开发需要哪些职场知识

职场是个人情世故的江湖&#xff0c;除了专业技能&#xff0c;成功的大数据开发人员还需要掌握多种职场知识。以下是一些重要的职场知识和技能&#xff0c;结合实际例子详细说明。 目录 理论知识与工程实践理论知识工程实践例子 项目经验总结项目管理总结和反思例子 做事方式方…

一招教你搞定Windows系统指定IP不变[固定IP地址方法]

1.打开控制面板&#xff0c;找到“网络和Internet” 点击进入&#xff1a; 2.点击打开“网络和共享中心”后&#xff0c;选择“更改适配器选项”。 3.点击 “查看此连接的状态”&#xff0c; 接着点击“详细信息” 查看信息。记录当前的IP地址是 10.88.x.xx&#xff0c;后面我们…

Linux驱动开发笔记(九)IIC子系统及其驱动

文章目录 前言一、IIC驱动框架二、总线驱动2.1 iic总线的运行机制2.2 重要数据结构2.2.1 i2c_driver结构体2.2.2 i2c总线结构体 2.3 匹配规则 三、设备树的修改四、设备驱动的编写4.1 相关API函数4.1.1 i2c_add_adapter( )4.1.2 i2c_register_driver( )4.1.3 i2c_transfer( )4.…

Spring+SpringMVC+MyBatis整合

目录 1.SSM介绍1.1 什么是SSM&#xff1f;1.2 SSM框架1.2.1 Spring1.2.2 SpringMVC1.2.3 MyBatis 2.SSM框架整合2.1 建库建表2.2 创建工程2.3 pom.xml2.4 log4j.properties2.5 db.properties2.6 applicationContext-dao.xml2.7.applicationContext-tx.xml2.8 applicationContex…

Redis-在springboot环境下执行lua脚本

文章目录 1、什么lua2、创建SpringBoot工程3、引入相关依赖4、创建LUA脚本5、创建配置类6、创建启动类7、创建测试类 1、什么lua “Lua”的英文全称是“Lightweight Userdata Abstraction Layer”&#xff0c;意思是“轻量级用户数据抽象层”。 2、创建SpringBoot工程 3、引入相…

新能源汽车CAN总线故障定位与干扰排除的几个方法

CAN总线是目前最受欢迎的现场总线之一,在新能源车中有广泛应用。新能源车的CAN总线故障和隐患将影响驾驶体验甚至行车安全,如何进行CAN总线故障定位及干扰排除呢? 目前,国内机动车保有量已经突破三亿大关。由于大量的燃油车带来严峻的环境问题,因此全面禁售燃油车的日程在…

汽车租赁系统

摘 要 随着汽车租赁市场的快速发展&#xff0c;为了提高汽车租赁服务的效率和用户体验&#xff0c;本论文设计与实现了一款基于Java的汽车租赁系统。 该系统采用B/S架构&#xff0c;利用JavaWeb技术和MySQL数据库实现了车辆信息管理、在线车辆租赁、门店出车模块、租赁订单信息…

Android Kotlin 中的闭包函数

闭包函数是现代编程语言中一个重要的概念&#xff0c;Kotlin 作为一种现代的 JVM 语言&#xff0c;自然也支持闭包函数。本文将详细介绍闭包函数的概念、在Kotlin 中的使用方法&#xff0c;以及一些常见的应用场景。 什么是闭包函数&#xff1f; 闭包函数&#xff0c;也称为闭…

React@16.x(42)路由v5.x(7)常见应用场景(4)- 路由切换动画

目录 1&#xff0c;实现路由切换基础样式 2&#xff0c;使用 CSSTransition 添加动画1&#xff0c;自定义动画组件 *TransitionRoute.jsx*2&#xff0c;*App.jsx*3&#xff0c;样式改动 3&#xff0c;注意点 通过一个例子来说明如何实现。 1&#xff0c;实现路由切换 基础样式…

亿发进销存管理系统+:多终端无缝协同,实现经营销售场景全覆盖

亿发软件凭借产品、市场、业务的深入理解&#xff0c;在进销存基础上进行了延伸&#xff0c;推出多终端、一体化的“进销存管理系统”多元产品矩阵。对企业经营中进货、出货、销售、付款等进行全程跟踪管理。有效辅助企业解决业务管理、销售管理、库存管理、财务管理等一系列问…