`
bowld
  • 浏览: 13963 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
最近访客 更多访客>>
社区版块
存档分类
最新评论

一个字符串问题的思考

阅读更多

  一、 问题描述: 
  求解给定文本text 中以字符 A 开头, 字符B 结尾的子串数量。例如,文本ABCAAB 中以A开头B结尾的子串分别为AB, ABCAAB, AAB, AB 共4个。
  二、 问题分析及算法设计:
  字符串问题求解的通用策略: 我从《TCPL》中学到的印象最深的一点,就是"逐字符处理"策略(同时注意 '\0'的处理)。
  首先,使用蛮力法求解: 设置两个下标 i , j ; i 指向已搜索到的 A 的位置, j 指向已搜索到的B的位置。 算法描述如下:
  STEP1: 从字符串最左边开始扫描,首先用i遍历搜索A: 若到达字符串末尾仍没有搜索到,则转至 STEP3; 
  若搜索到A则停止,转至STEP2;
  STEP2: 初始化j为 i 的下一个位置并开始搜索B: 若到达字符串末尾没有搜索到,则 i 移到下一个位置并转至STEP1;
  若搜索到B,则子串数目次数加一,并继续向前搜索,直到字符串末尾并转至STEP1;
  STEP3: 搜索结束,退出算法。
  这样的蛮力算法,其时间效率取决于A在字符串中出现的次数Na, T(n) = O(n * Na), n 是字符串长度。 也可以从右往左搜索, 其时间效率为 T(n) = O(n * Nb), Nb是 B 在字符串中出现的次数。 如果知道A和B在文本中出现的次数,那么就可以选择从左往右或者从右往左的遍历方式。最差情况下,例如全A串,则效率为O(n*n). 
  有没有可能找到更好的算法呢? 
  我们知道,分治算法通常具有 O(nlogn)的效率,那么,这个问题是否可以采用分治法来求解呢? 答案是肯定的。
  假设 N[left: right] 是文本 text[left:right] 中以A开始B结尾的子串数量, 则整个文本中以A开始B结尾的子串数量为 
  N[0:n] = N[0:n/2] + N[n/2+1: n] + Na * Nb. Na 是 A 在 text[0:n/2]中出现的次数, Nb 是 B 在 text[n/2+1: n] 中出现的次数;因为在后半段文本中的所有B都出现在前半段文本中的所有A之后(每个A与每个B都形成合乎要求的子串),而在后半段文本中的所有A出现在前半段文本中的所有B之后(每个A与每个B都不能形成合乎要求的子串),所以, 两半段文本合并时的子串数量应该是Na * Nb.这样,一个递归分治算法的模型基本就出来了,其时间复杂度为 T(n) = 2T(n/2) + O(n) = O(nlogn).
  例子: N(ABCAAB) = N(ABC) + N(AAB) + 1 * 1 = 4
  N(ABC) = N (A) + N(BC) + 1 * 1 = 1
  N(AAB) = N(A) + N(AB) + 1 * 1 = 2
  N(BC) = N(B) + N(C) + 0 * 0 = 0
  N(AB) = N(A) + N(B) + 1 * 1 = 1
  因此,ABCAAB 中以A开头B结尾的子串数量有4个。
  能不能找到线性时间的算法呢? 看上去似乎有可能,却又不是那么明显。在《编程珠玑I》的算法设计技术那一章里,作者采用四种算法(本质上是三种不同类型的算法)分别进行了设计,并在小结中提到: 凡是数组问题,都可以考虑动态规划法来求解。那么,是否适用于本题呢?
  假设给定文本中前 n 个字符组成的文本中以A开头B结尾的子串数量为Count[n], A出现的次数为 Na, 则前 n+1 个字符组成的文本中以A开头B结尾的子串数量为:
  [1] 若第 n+1 个字符不是结尾字符B,那么, Count[n+1] = Count[n]; 
  [2] 若第 n+1 个字符是结尾字符B,那么, Count[n+1] = Count[n] + Na.
  这样,我们推导出了解的递推关系式。更进一步地,首先初始化子串数量count及开头字符出现次数beginNum为0;接着,从第一个字符开始,若该字符是开头字符,则beginNum++; 若字符是结尾字符,则count += beginNum; 若该字符既不是结尾字符也不结束字符,那么,count, beginNum均不变;
  注意,当开头字符和结尾字符相同时,上述算法会出现问题。此时,只要计算开头字符出现的次数Na, 则子串数量应为 Na*(Na-1)/2.
  例子: ABCAAB
  [1] count = 0; Na = 0; 
  [2] 搜索到A: count = 0; Na = 1; 
  [3] 搜索到B: count += Na → count = 1; Na = 1;
  [4] 搜索到C: count = 1; Na = 1; 
  [5] 搜索到A: count = 1; Na = 2;
  [6] 搜索到A: count = 1; Na = 3;
  [7] 搜索到B: count += Na → count = 4; Na = 3;
  因此,ABCAAB 中以A开头B结尾的子串数量有4个。
  三、 完整程序:
  /* * substrNum.c : 此程序计算给定文本中以给定字符开头和结尾的字串的数目 * 比如 ABCAAB 中, 以A开头B结尾的子串有 AB, ABCAAB, AAB, AB 4个。 */ #include  #include  #include  int substrnum(char *, char, char); int substrnumDIV(char* text, char beginc, char endc); int substrnumDYN(char* text, char beginc, char endc); int substrnumREC(char* text, int leftIndex, int rightIndex, char beginc, char endc); void test(int (*fun)(char*, char, char)); int main() { printf("\n ******* 使用蛮力求解 ******** \n"); test(substrnum); printf("\n ******* 使用分治法求解 ******* \n"); test(substrnumDIV); printf("\n ******** 使用动态规划法求解 ******* \n"); test(substrnumDYN); return 0; } /* * substrnum: 在给定文本 text 中以字母 beginc 开头, 字母 endc 结尾的子串 * 蛮力法求解: 算法时间 O(n*Na) Na, 是开始字符在字符串中出现的次数。 * 或 从右向左扫描, O(n*Nb), Nb 是结尾字符在字符串中出现的次数 */ int substrnum(char *text, char beginc, char endc) { assert(text != NULL); int i = 0 , j; int count = 0; char *p = text; while (1) { while (*(p+i) && (*(p+i) != beginc)) { i++; } if (*(p+i) == '\0') break; j = i+1; while (*(p+j)) { if (*(p+j) == endc) { count++; } j++; } i++; } return count; } /* * substrnumDYN: 在给定文本 text 中以字母 beginc 开头, 字母 endc 结尾的子串 * 使用动态规划法求解: * 若给定文本的前 n 个字符组成的字符串中所含子串数目为 count[n], 开始字符出现次数为 beginNum; * 则前 n+1 个字符串中所含子串数目为: * 1. 若第 n+1 个字符不是结尾字符,则 count[n+1] = count[n]; * 2. 若第 n+1 个字符是结尾字符,则 count[n+1] = count[n] + beginNum * */ int substrnumDYN(char *text, char beginc, char endc) { assert(text != NULL); char *p = text; int count = 0; int beginNum = 0; while(*p) { if (*p == beginc) { // 情况 1: 该字符是开始字符 beginNum++; } else if (*p == endc) { // 情况 2 : 该字符是结尾字符 count += beginNum; } p++; // 情况 1: 该字符既不是开始字符也不是结尾字符 } if (beginc == endc) { // 开始字符与结尾字符相同 return beginNum * (beginNum - 1) / 2; } return count; } /* * substrnumDIV: 在给定文本 text 中以字母 beginc 开头, 字母 endc 结尾的子串 * 使用分治法求解: * 将给定文本分成两端 text[0: n/2] 和 text[n/2+1: n-1], 则子串数量为: * N(text) = N(text[0:n/2]) + N(text[n/2+1: n-1]) + NB * NE * 其中, NB是开始字符在 text[0:n/2] 中出现的次数, NE 是 结尾字符在 text[n/2: n-1] 中出现的次数 * 算法时间:T(n) = 2T(n/2) + n = O(nlogn) */ int substrnumDIV(char* text, char beginc, char endc) { assert(text != NULL); return substrnumREC(text, 0, strlen(text)+1, beginc, endc); } int substrnumREC(char* text, int leftIndex, int rightIndex, char beginc, char endc) { if (rightIndex == leftIndex) { return 0; } else { int mid = (rightIndex - leftIndex + 1) / 2 + leftIndex; char *p = text + leftIndex; char *q = text + mid; int nb = 0; int ne = 0; while (*p && p 字符串问题的例子是为了说明什么呢? 主要是因为,我认为它非常生动地演示了字符串问题的几种主要思路和算法: 蛮力法、分治法、动态规划法。另外,还有一种算法,是对字符串进行预处理,比如高效字符串匹配KMP算法就是这么做的。
  在这个问题中,可以首先遍历一次文本(时间是O(n)), 分别将A,B出现的位置存储在两个数组中。仍以ABCAAB为例: 首先可求得 a[]: 1, 4, 5 ; b[]: = 2, 6. 接着,求a与b的笛卡尔乘积中 (a,b) (a < b) 的个数即可。可以在O(len(a) + len(b)) 的时间内求解,只是在遍历过程中有点技巧。因此,整个算法的时间复杂度是O(n).
分享到:
评论

相关推荐

    从键盘输入一个字符串,如“www.moe.gov.cn”,编写程序,实现如下功能

    (5)输出字符“o”在字符串中第一个位置的索引值。(如没有该字符则返回-1)(6)输出字符“o”出现的总次数。(7)将字符串中所有的“.”替换为“-”并输出。(8)将字符串中所有的字母转换为大写字母并输出。(9)...

    比较两个字符串大小汇编语言源代码加实验报告

    用汇编语言写的比较字符串大小的源代码,加实验报告,实验报告可是本人亲自撰写的,代码绝对可以运行!!自己上汇编实验课写的。

    汇编语言2个字符串的比较

    输入两个字符串,比较它们是否相同,若相同则输出Match,不同则输出No Match

    数组与字符串.docx

    2) 熟悉Java中字符串的使用。 1)数组的基本操作,包括创建数组,填充数组,访问数组,拷贝数组,数组排序,数组查找。 2)编写一个猜密码的小程序,规则如下:程序首先产生一个三位数的密码,例如“025”,用户每次...

    如何求字符串里的最长回文子串

    给一个字符串,求出它的一个最长的回文子串.所谓回文子串,指的是一个字符串从左到右和从右到左遍历得到的序列是相同的.例如”abcba”是回文子串,而”abcab”就不是回文子串. 思考 如何确定一个字符串是回文串?这是一...

    c#压缩字符串的方法

    大家知道trade中一般会有订单来源,省市区 ,当把这些字段灌进去后,你会发现他们特别侵蚀内存,因为都是字符串类型,不知道大家对内存侵蚀性是不是很清楚,我就问一个问题。 Question: 一个空字符串占用多大内存? ...

    一些C语言中字符串的算法问题解决实例小结

     具体描述,如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个...

    算法修炼之路—【字符串】Leetcode 345 反转字符串中的元音字母

    编写一个函数,以字符串作为输入,反转该字符串中的元音字母。 示例1: 输入: s = “hello” 输出: “holle” 示例2: 输入: s = “leetcode” 输出: “leotcede” 思路分析 难度是简单 ,我们首先要明确元音...

    Python 字节流,字符串,十六进制相互转换实例(binascii,bytes)

    最近做一个项目,是用Python进行相关的串口操作。及将相关指令通过串口发给设备,设备根据发过来的指令来做出相应的操作,所用的库是Pyserial。在最初开发时,出现的问题在于:别人给的文档里面的命令是十六进制的。...

    Go 高效截取字符串的一些思考

    主要介绍了Go 高效截取字符串的一些思考,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    Replace关键字的妙用查询是否包含某个特定字符串

    在sql server中Replace...这里我们就思考用Replace替换掉字符串中的“aaa”,如果能够替换掉,那就证明该字符串中含有这个字符,替换掉了以后肯定和原字符串不一样,因此有了下面的SQL出炉。 代码如下: SELECT * FRO

    python实现将字符串中的数字提取出来然后求和

    因工作原因,很久没有学习python知识了,感觉都快忘记了,前天看到一个练习题,如何将字符串中的数字提取出来,然后求和呢?下面我来解释一下如何通过python代码来实现。 题目:字符串43…3y2.f67se2.666. 将其中的...

    超完整汇编子程序小实验(附思考题)

    汇编子程序小实验 编写程序,将字符串STING1的内容复制到字符串STRING2中。要求由子程序来实现字符串的复制,并采用寄存器来传递参数。

    c#写个小猫猜数,一起玩玩吧,通过小猫猜数来学习数组与字符串的运用

    内容概要:通过带着读者手写一个简单的游戏案例,了解 c#的数组与字符串。在手写游戏案例的过程中学会数组在实际开发过程中的运用,字符的运用方式。同时读者在写的过程中也可以思考如何将程序可以更精简,运行效率...

    复习Python中的字符串知识点

    只要将所需的文本放入一对引号中,就完成了一个新字符串的创建(参见清单 1)。如果稍加思考的话,您可能会感到有些困惑。毕竟,有两类可以使用的引号:单引号 (‘) 和双引号 (“)。幸运的是,Python 再一次使这种...

    情人节祝福生成器(Python + 字符串操作 + 随机数生成 + 文件操作)

    该代码使用了字符串操作和随机数生成来生成随机的情人节祝福语。生成的祝福语会保存到一个文本文件中,方便后续使用或分享。用户可以根据需要自定义祝福语的数量和保存文件的路径。代码中使用了Python的标准库,无需...

    深挖 Redis 6.0 源码—SDS.docx

    我们首先考虑一个问题,如何实现一个二进制安全的字符串? 在 C 语言中,\0 表示字符串结束,如果字符串中本身就包含 \0 字符,那么字符串就会在 \0 处被截断,即非二进制安全;若通过使用一个 len 属性,来判断字符...

    Python中字符串的修改及传参详解

    最近在一次使用python实现字符串反转的时候,发现写出的代码居然是错误的,于是通过思考后决定要总结下这次的经历,于是写了这篇文章,本文的内容主要给大家介绍了Python中字符串的修改及传参,有需要的朋友们可以...

    字符串的分割+截取——引起一只java小白的思考

    文章目录分割+截取的简单使用简单分割简单截取升级版分割+截取的使用url分割id截取(或者截取其他由某种规则拼接起来的串儿)结尾... //给定一个字符串 String[] split = str.split(-); //将字符串按照某一规则分割 /

    Java实验报告一java基础.doc

    2、编写一个Java程序,输入两个字符串,计算这两个字符串的长度,并对这两个字符串进行连接、比较大小。 3、输入任意一天(按格式“yyyy-mm-dd”),计算该天是本年中的第几天? 思考题 1、如何产生a~b的随机整数? ...

Global site tag (gtag.js) - Google Analytics