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

题目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 SUBSTRint main() {
#ifdef SUBSTRSubStr 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;}
#endifreturn 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 SUBSTRint main() {#ifdef SUBSTRstring 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;}#endifreturn 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 SUBSTRint main() {#ifdef SUBSTRstring 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;}#endifreturn 0;
}

运行结果:

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

 

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

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

相关文章

零基础STM32单片机编程入门(七)定时器PWM波输出实战含源码视频

文章目录 一.概要二.PWM产生框架图三.CubeMX配置一个TIME输出1KHZ&#xff0c;占空比50%PWM波例程1.硬件准备2.创建工程3.测量波形结果 四.CubeMX工程源代码下载五.讲解视频链接地址六.小结 一.概要 脉冲宽度调制(PWM)&#xff0c;是英文“Pulse Width Modulation”的缩写&…

「ETL趋势」FDL定时任务区分开发/生产模式、API输入输出支持自定义响应解析

FineDataLink作为一款市场上的顶尖ETL工具&#xff0c;集实时数据同步、ELT/ETL数据处理、数据服务和系统管理于一体的数据集成工具&#xff0c;进行了新的维护迭代。本文把FDL4.1.7最新功能作了介绍&#xff0c;方便大家对比&#xff1a;&#xff08;产品更新详情&#xff1a;…

leetcode--二叉树中的最长交错路径

leetcode地址&#xff1a;二叉树中的最长交错路径 给你一棵以 root 为根的二叉树&#xff0c;二叉树中的交错路径定义如下&#xff1a; 选择二叉树中 任意 节点和一个方向&#xff08;左或者右&#xff09;。 如果前进方向为右&#xff0c;那么移动到当前节点的的右子节点&…

Redis 中的通用命令(命令的返回值、复杂度、注意事项及操作演示)

Redis 中的通用命令(高频率操作) 文章目录 Redis 中的通用命令(高频率操作)Redis 的数据类型redis-cli 命令Keys 命令Exists 命令Expire 命令Ttl 命令Type命令 Redis 的数据类型 Redis 支持多种数据类型&#xff0c;整体来说&#xff0c;Redis 是一个键值对结构的&#xff0c;…

智能光伏开发都能用到什么软件和工具?

随着全球对可再生能源的日益重视和光伏技术的快速发展&#xff0c;智能光伏开发已成为推动能源转型的重要力量。在光伏项目的全生命周期中&#xff0c;从设计、建设到运营管理&#xff0c;各种软件和工具的应用发挥着至关重要的作用。 一、光伏系统设计软件 1、PVsyst PVsyst…

【ECCV 2024】首个跨模态步态识别框架:Camera-LiDAR Cross-modality Gait Recognition

【ECCV 2024】首个跨模态步态识别框架&#xff1a;Camera-LiDAR Cross-modality Gait Recognition 简介&#xff1a;主要方法&#xff1a;实验结果&#xff1a; 论文&#xff1a;https://arxiv.org/abs/2407.02038 简介&#xff1a; 步态识别是一种重要的生物特征识别技术。基…

16_更快的速度与精度:Faster R-CNN

回顾R-CNN:链接 回顾Fast R-CNN:链接 1.1 简介 Faster R-CNN是作者Ross Girshick继Fast R-CNN后的又一力作。同样使用VGG16作推理速度在GPU上达到5fps(包括候选区域的生成)&#xff0c;准确率为网络的backbone&#xff0c;也有进一步的提升。在2015年的ILSVRC以及COCO竞赛中…

狂赚三个亿,百亿医用耗材上市公司重金押注老人轮椅

布局海外市场&#xff0c;轮椅销量翻两番 作者 | 艾米莉 排版 | 张思琪 抛砖引玉 1.年销售60万台轮椅&#xff0c;英科医疗如何做到&#xff1f; 2.老年人轮椅是出海&#xff0c;还是深耕国内市场&#xff1f; 3.2022年全球轮椅市场规模为48亿美元&#xff0c;谁在喝汤&…

一文讲解Docker入门到精通

一、引入 1、什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;它允许在一台物理机上创建多个独立的虚拟环境&#xff0c;这些环境被称为虚拟机&#xff08;VM&#xff09;。每个虚拟机都可以…

Node.js的下载、安装和配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

vue 组件el-tree添加结构指示线条

效果展示: 注意&#xff1a;组件中需要添加:indent"0" 进行子级缩进处理&#xff0c;否则会出现子级缩进逐级递增 :expand-on-click-node"false" 设置点击箭头图标才会展开或者收起 代码&#xff1a; <el-tree class"tree filter-tree" :da…

【解码现代 C++】:实现自己的智能 【String 类】

目录 1. 经典的String类问题 1.1 构造函数 小李的理解 1.2 析构函数 小李的理解 1.3 测试函数 小李的理解 1.4 需要记住的知识点 2. 浅拷贝 2.1 什么是浅拷贝 小李的理解 2.2 需要记住的知识点 3. 深拷贝 3.1 传统版写法的String类 3.1.1 拷贝构造函数 小李的理…

入门PHP就来我这(纯干货)08

~~~~ 有胆量你就来跟着路老师卷起来&#xff01; -- 纯干货&#xff0c;技术知识分享 ~~~~ 路老师给大家分享PHP语言的知识了&#xff0c;旨在想让大家入门PHP&#xff0c;并深入了解PHP语言。 1 PHP对象的高级应用 1.1 final关键字 final 最终的、最后的。被final修饰过的类…

LabVIEW汽车ECU测试系统

开发了一个基于LabVIEW开发的汽车发动机控制单元&#xff08;ECU&#xff09;测试系统。该系统使用了NI的硬件和LabVIEW软件&#xff0c;能够自动执行ECU的功能测试和性能测试&#xff0c;确保其在不同工作条件下的可靠性和功能性。通过自动化测试系统&#xff0c;大大提高了测…

【docker nvidia/cuda】ubuntu20.04安装docker踩坑记录

docker nvidia 1.遇到这个错误&#xff0c;直接上魔法(科学上网) OpenSSL SSL_connect: Could not connect to nvidia.github.io:443 这个error是运行 NVIDIA官方docker安装教程 第一个 curl 命令是遇到的 2. apt-get 更新 sudo apt update遇到 error https://download.do…

CDC实时同步进行时遇到不可抗力中断了怎么办?

目录 一、CDC技术的概念 二、CDC技术的应用场景 1.数据复制和同步 2.实时数据仓库 3.业务过程监控和审计 4.ETL 进程优化 三、CDC与数据管道的关系 1.区别 CDC&#xff08;Change Data Capture&#xff09; 数据管道&#xff08;Data Pipeline&#xff09; 2.联系 CDC是数据管道…

4面体空间5点结构种类与占比

在30个点的4面体中取5个点&#xff0c;有30*29*28*27*26/(5*4*3*2)142506种取法&#xff0c; 这里要求5个点必须是直链或支链。共有496个组合符合要求&#xff0c;按平移对称性可分成181个不同的结构 结构 数量 结构 数量 结构 数量 结构 数量 结构 数量 结构 数量 …

深入分析 Android BroadcastReceiver (九)

文章目录 深入分析 Android BroadcastReceiver (九)1. Android 广播机制的扩展应用与高级优化1.1 广播机制的扩展应用1.1.1 示例&#xff1a;有序广播1.1.2 示例&#xff1a;粘性广播1.1.3 示例&#xff1a;局部广播 1.2 广播机制的高级优化1.2.1 示例&#xff1a;使用 Pending…

【C++】 解决 C++ 语言报错:Double Free or Corruption

文章目录 引言 双重释放或内存破坏&#xff08;Double Free or Corruption&#xff09;是 C 编程中常见且严重的内存管理问题。当程序尝试多次释放同一块内存或对已经释放的内存进行操作时&#xff0c;就会导致双重释放或内存破坏错误。这种错误不仅会导致程序崩溃&#xff0c…

跑腿平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;基础数据管理&#xff0c;管理员管理&#xff0c;接单详情管理&#xff0c;跑腿员管理&#xff0c;跑腿任务管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;跑腿任务&#xff0c;接单员&…