递归(三)—— 初识暴力递归之“字符串的全部子序列”

news2024/11/19 14:36:51

题目1 : 打印一个字符串的全部子序列

题目分析:

解法1:非递归方法

我们通过一个实例来理解题意,假设字符串str = “abc”,那么它的子序列都有那些呢?" ", “a”, “b”,“ab”,"c","ac","bc","abc", 一共8个,其中有一个是空串。怎么得到这个结果的?我的眼就是尺,看一眼就知道了。但如果这个字符串的长度是10呢,30呢?看一眼怎么够!除了多看几眼,是不是也要找个纸、笔写写画画呢?

第一笔: a 的存在情况有两种:存在 或 不存在;

第二笔: b 的存在情况有两种:存在 或 不存在;

第三笔: c 的存在情况有两种:存在 或 不存在;

这三笔当然不能各自为营的画在纸上,第二笔和第三笔是在前一笔的基础上画出的。

第二笔:画出b的情况

第三笔:画出c的情况

现在,通过上图我们得到了子字符串的所有结果,"abc","ab","ac","a","bc","b","c","" 如果字符串str = "abcd",我们也有思路落下第四笔,即在'c' 的下方,给每一个字符串补充上d的存在与不存在这两个情况,于此同时,我们也总结出了计算子字符串的规律:

1.  从原字符串str 的index== 0 开始遍历字符串,直到index == 原字符串长度 结束。 (即:‘a’\rightarrow‘c’)

2. 当index == i 时, str[i] 的两种情况(存在或不存在)是添加到index == i-1 所得出的子串基础上的,这一条规律也是迭代的关键。

代码实现

//代码段1
#include <string>
#include <List>

using std::string;
using std::list;

class SubStr {
public:
	void process_1(string strPre, list<string>& ans);
	
};

void SubStr::process_1(string strPre,list<string>& ans) {
	if (strPre.length() < 1) return;
	string str = "";
	str = str + strPre[0];
	ans.push_back(str);
	ans.push_back("");
	
	for (size_t j = 1; j < strPre.length(); j++) {
		int count = ans.size();
		for (list<string>::iterator item = ans.begin();  item !=  ans.end(); item++) {
			ans.push_back(*item + strPre[j]);
			count--;
			if (!count)break;
		}
	}
	return;
}

#define SUBSTR

int main() {
#ifdef SUBSTR
    SubStr subStrObj;
    string str = "abc";
    list<string> ans;
    
    subStrObj.process_1(str, ans);
    for (list<string>::iterator item = ans.begin(); item != ans.end(); item++) {
        std::cout << *item << std::endl;
    }
#endif
    return 0;
}

运行结果

解法2:递归方法

在解法1中我们已经明白了子序列的含义,也明白了对于字符串str上任意位置上的字符str[i] 在子序列中都有两种可能性,即存在与不存在。假设 str = “abc”, 初始状态就是str[0~2] 字符都不存在的状态,即空串。

我们用枚举的方式,找到所有可能性,见表1.

我们给出的初始状态是各位都不存在,那么对于str[2] 来说,str[0~1] 不存在是确定的,是不可更改的事实,但是str[2]可选择,可以是不存在,例如子序列1;也可以是存在,例如子序列2.

到此,在str[0~1]不存在的条件下,所有的可能性都找到了:ans = {“”, “c”}

继续枚举,str[0]不存在,str[1]存在的条件下,str[2] 的可能性?见子序列3 和 子序列4.

ans = {"", "c", "b", "bc"};

继续枚举,str[0]存在,其后str[1~2] 的各种可能性:子序列5 ~ 子序列8;

ans={"", "c", "b", "bc", "a", "ac","ab","abc"}

表二中给出了当字符串的长度增加的枚举情况,str= "abcd";

表1
子序列abc子序列值
1\times\times\times" "
2\times\times\veec
3\times\vee\timesb
4\times\vee\veebc
5\vee\times\timesa
6\vee\times\veeac
7\vee\vee\timesab
8\vee\vee\veeabc
表2
子序列abcd子序列值
1\times\times\times\times" "
2\times\times\times\veed
3\times\times\vee\timesc
4\times\times\vee\veecd
5\times\vee\times\timesb
6\times\vee\times\veebc
7\times\vee\vee\timesbc
8\times\vee\vee\veebcd
9\vee\times\times\timesa
10\vee\times\times\veead
11\vee\times\vee\timesac
12\vee\times\vee\veeacd
13\vee\vee\times\timesab
14\vee\vee\times\veeabc
15\vee\vee\vee\timesabc
16\vee\vee\vee\veeabcd

通过两个表的枚举情况,我们可以得出这样的结论:

1. 每一个子串都隐含了各个字符的存在情况,比如表2中的子序列3 == ”c“, 其实用真值表值来表示的话,它就是0010, 第3位为1,表示c存在,其他位为0,表示不存在,也就是说我们遍历,产生子序列的时候,是对原字符串的每个字符都会遍历到的,只是选择它在子序列中存在或不存在。

2. 当遍历到第i位时, 我们认为 0 ~ i-1 位的情况是已经确定的,第i位以及其后面的各位的情况是有待枚举的。例如表2中子序列13~ 16, str[0~1] 已经确定,即存在,枚举出str[2~3] 的所有情况。

代码实现

//代码段2
#include <string>
#include <List>

using std::string;
using std::list;

class SubStr {
public:
	void process_2(string strPre, int index, list<string>& ans, string path);
};
/*
strPre: 原字符串
index:表示字符串的下标,即位置,当然来到了strPre[index];
ans:存放子序列
path:strPre[0~index-1] 的字符情况
*/
void SubStr::process_2(string strPre, int index, list<string>& ans, string path) {
	if (index == strPre.length()){  //已经遍历完了整个路径,即strPre[0 ~ strPer.length()-1]都遍历了,确定了各位的存在与否, 将这个路径存入结构体
		ans.push_back(path);
		return;
	}
	
	process_2(strPre, index + 1, ans, path); // 此子序列中strPre[index]不存在,即没有要strPre[index]字符
	process_2(strPre, index + 1, ans, path + strPre[index]);// 此子序列中strPre[index]存在,即要了strPre[index]字符	
}

#define SUBSTR

int main() {

#ifdef SUBSTR
    string str = "abc";
    list<string> ans;
    string path = "";
    SubStr subStrObj;
    subStrObj.process_2(str, 0, ans, path);
    std::cout << "子序列的数量是:" << ans.size() << std::endl;
    int count = 0;
    for (list<string>::iterator item = ans.begin(); item != ans.end(); item++) {
        std::cout <<"sub string " << ++count << ": "<<  * item << std::endl;
    }
    
#endif
    return 0;
}

运行结果:

表1:

表2

小技巧:

代码中使用了STL容器来存放结果ans, 但是为什么使用list 而不是vector呢?在编写代码时具体使用什么数据结构,需要根据实际的数据类型、数据规模以及其对数据所要执行的操作特点来决定。

vector具有连续的内存空间,支持随机访问,如果需要高效的随机访问,而很少使用插入和删除的操作,使用vector

list 具有一段不连续的内存空间,如果需要高频次的插入和删除操作,较少执行随机存取,使用list。

本题目的场景更适合使用list。

补充问题:打印一个字符串的全部子序列,要求不出现重复子序列。

问题分析:

这个问题可以分两个思路来解决:

1. 使用代码段2中的函数process_2 找到所有的子序列,在打印时做去重处理;

2. 定义函数precess_3, 函数实现思路与process_2一样,只是用于存放子序列的数据结构由list改为set, 因为set 不存放相同的数据元素。

我们完成process_3 的代码实现。

代码实现:

//代码段3
#include <string>
#include <List>
#include <set>
using std::string;
using std::list;
using std::set;

class SubStr {
public:
	void process_3(string strPre, int index, set<string>& ans, string path);
};
/*
strPre: 原字符串
index:表示字符串的下标,即位置,当然来到了strPre[index];
ans:存放子序列
path:strPre[0~index-1] 的字符情况
*/
void SubStr::process_3(string strPre, int index, list<string>& ans, string path) {
	if (index == strPre.length()){  //已经遍历完了整个路径,即strPre[0 ~ strPer.length()-1]都遍历了,确定了各位的存在与否, 将这个路径存入结构体
		ans.insert(path);
		return;
	}
	
	process_3(strPre, index + 1, ans, path); // 此子序列中strPre[index]不存在,即没有要strPre[index]字符
	process_3(strPre, index + 1, ans, path + strPre[index]);// 此子序列中strPre[index]存在,即要了strPre[index]字符	
}

#define SUBSTR

int main() {

#ifdef SUBSTR
    string str = "abcc";
    set<string> ans;
    string path = "";
    SubStr subStrObj;
    subStrObj.process_3(str, 0, ans, path);
    for (list<string>::iterator item = ans.begin(); item != ans.end(); item++) {
        std::cout << *item << std::endl;
    }
    
#endif
    return 0;
}

运行结果:

如果用代码段2(没有去重功能)打印str="abcc" 的子序列:

 

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

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

相关文章

做有一个有表情且会动的 Finder

作为一只合格的互联网巡回猎犬&#xff0c;今天给大家分享一个有趣且无聊的小工具&#xff0c;摸鱼发呆必备&#xff0c;可以说是一件「无用良品」了。 软件介绍 Mouse Finder 长的跟访达差不多&#xff0c;功能也一样&#xff0c;但有一个重要区别&#xff1a;眼睛会跟随鼠标…

视频参考帧和重构帧复用

1、 视频编码中的参考帧和重构帧 从下图的编码框架可以看出&#xff0c;每编码一帧需要先使用当前帧CU(n)减去当前帧的参考帧CU&#xff08;n&#xff09;得到残差。同时&#xff0c;需要将当前帧的重构帧CU*&#xff08;n&#xff09;输出&#xff0c;然后再读取重构帧进行预测…

Linux存储管理I

存储管理I 一 .存储管理 主要知识点: 基本分区、逻辑卷LVM、EXT3/4/XFS文件系统、RAID 1.1.初识硬盘 机械 HDD(Hard Disk Drive) 固态 SSD(Solid State Drive):优点&#xff1a;读写速度快、防震抗摔性、低功耗、无噪音、工作温度范围大、轻便&#xff1b;缺点&#xff1a;容…

笔记:SpringBoot+Vue全栈开发2

笔记&#xff1a;SpringBootVue全栈开发2 1. MVVM模式2. Vue组件化开发3. 第三方组件element-ui的使用4. axios网络请求5. 前端路由VueRouter 1. MVVM模式 MVVM是Model-View-ViewModel的缩写&#xff0c;是一种基于前端开发的架构模式&#xff0c;其核心是提供对View和ViewMod…

Shopee(虾皮)怎么获取流量?

店铺流量的高低会直接关联到卖家店铺单量&#xff0c;也关系到一个店铺的营业情况和利润&#xff0c;那么Shopee的流量从哪里来呢&#xff1f; Shopee的平台流量可分为五个部分&#xff1a; 1.自然流量 2.关键字广告流量 3.平台活动流量 4.营销流量 5.粉丝流量 怎么提升…

Vue + SpringBoot:el-upload组件单文件、多文件上传实战解析

文章目录 单文件上传后端前端 多文件上传后端前端 单文件上传 后端 PostMapping("/uploadDxfFile") public R uploadDxfFile(RequestParam(value "file", required true) MultipartFile multipartFile) throws Exception {// 文件校验工作if (multipar…

类和对象深入理解

目录 static成员概念静态成员变量面试题补充代码1代码2代码3如何访问private中的成员变量 静态成员函数静态成员函数没有this指针 特性 友元友元函数友元类 内部类特性1特性2 匿名对象拷贝对象时的一些编译器优化 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接…

Qtgui编程基础

Qt简介 ( 框架5.9.8版本 ) Qt是源代码级的跨平台一次编写到处编译.一次开发的Qt应用程序可以移值到不同平台. Qt体系架构 Qt的整个设计都是以单根继承为主这跟java相同.所谓单根继承就是说所有的Qt类都有一个共同的祖先都是QObject类QObject类后面有三个大的子类分别负责不同…

厌倦了Nvim、vim等命令行编辑器?来看看新血脉....

是否厌倦了那几款烂大街的命令行风格编辑器&#xff1f;今天就来给各位换换血&#xff0c;介绍几个新成员。 让我们深入了解这些文本编辑器的主要功能和优点&#xff1a; 1. Ox Editor&#xff1a;优雅的新秀 Ox Editor是一款新兴的终端文本编辑器&#xff0c;以其简洁和优雅…

汽车IVI中控开发入门及进阶(三十三):i.MX linux开发之开发板

前言: 大部分物料/芯片,不管MCU 还是SoC,都会有原厂提供配套开发板,有这样一个使用原型,在遇到问题时或者进行开发时可以使用。 i.MX 8QuadXPlus MEK board: 1、要测试display显示器,可使用i.MX mini SAS将“LVDS1_CH0”端口连接到LVDS到HDMI适配器的cable。 2、要测试…

接口参数化--代码支撑参数

如果测试的用例里传动态参数&#xff0c;就需要把列出规则&#xff0c;然后在代码里运用前期是把动态参数都列出了&#xff0c;现在需要运用 步骤&#xff1a; 先excel表中定义规范&#xff0c;将请求参数里的时间戳定义规则&#xff08;规范也需要提前写出&#xff09; 建立…

库表设计(基础)-实体与设计关系

实体关系分析 1 实体关系是指系统事务之间的联系。 2 实体关系需要双向分析。 3 实体关系决定表关系。 实体关系的种类 1 一对一 2 一对多 3 多对多 举例&#xff1a; 上面关系如下&#xff1a; 班级和学生 &#xff1a; 1:N 学生和课程&#xff1a;N : N 学生和学籍档案&a…

【MotionCap】pycharm 远程在wsl2 ubuntu20.04中root的miniconda3环境

pycharm wsl2 链接到pycharmsbin 都能看到内容,/root 下内容赋予了zhangbin 所有,pycharm还是看不到/root 下内容。sudo 安装了miniconda3 引发了这些问题 由于是在 root 用户安装的miniconda3 所以安装路径在/root/miniconda3 里 这导致了环境也是root用户的,会触发告警 WA…

温州网站建设方案及报价

随着互联网的发展&#xff0c;网站建设已经成为企业推广和营销的重要手段。温州作为中国经济发达地区之一&#xff0c;各行各业企业纷纷意识到网站建设的重要性&#xff0c;纷纷加大网站建设工作的投入。那么&#xff0c;温州网站建设方案及报价是怎样的呢&#xff1f;下面我们…

昇思25天学习打卡营第10天|ResNet50迁移学习

文章目录 昇思MindSpore应用实践基于MindSpore的ResNet50迁移学习1、迁移学习简介2、加载ImageNet数据集数据集可视化 3、ResNet50 模型4、模型训练固定特征进行训练 5、模型推理 Reference 昇思MindSpore应用实践 本系列文章主要用于记录昇思25天学习打卡营的学习心得。 基于…

AI革命:RAG技术引领未来智能

AI革命:RAG技术引领未来智能 在人工智能的浪潮中,一种名为RAG(Retrieval-Augmented Generation)的技术正在悄然改变我们的世界。这种技术通过整合外部知识库,极大地增强了大型语言模型(LLM)的性能,为智能助手、聊天机器人等应用带来了革命性的提升。 1 突破性的RAG技…

迈威通信本安Wi-Fi 6工业无线AP系列,促进井下无线全覆盖

在现代化的工业生产中&#xff0c;无线通信技术的应用日益广泛。特别是对于矿井等复杂环境&#xff0c;传统的有线通信方式往往面临着布线困难、维护成本高、灵活性差等问题。为了解决这些难题&#xff0c;迈威通信推出了本安Wi-Fi 6工业无线AP系列&#xff0c;以其卓越的性能和…

《C++20设计模式》外观模式

文章目录 一、前言二、实现1、UML类图2、实现 一、前言 一句话总结外观模式&#xff1a;简化接口&#xff0c;或者简化流程。&#x1f642; 相关代码可以在这里&#xff0c;如有帮助给个star&#xff01;AidenYuanDev/design_patterns_in_modern_Cpp_20 二、实现 原来需要很…

idea MarketPlace插件找不到

一、背景 好久没用idea了&#xff0c;打开项目后没有lombok&#xff0c;安装lombok插件时发现idea MarketPlace插件市场找不到&#xff0c;需要重新配置代理源&#xff0c;在外网访问时通过代理服务进行连接 二、操作 ### File-->setting 快捷键 Ctrl Alt S 远端源地…

怎么做外贸推广:10个详细教程和工具

1. 介绍 1.1 什么是外贸推广 外贸推广指的是将产品或服务推广到国际市场的过程。它的主要目的是吸引海外客户&#xff0c;增加销售额&#xff0c;并扩大企业的全球影响力。外贸推广不仅仅是销售产品&#xff0c;它还包括品牌建设、市场研究和客户关系管理。 谷歌外贸推广案例…