字符串相关问题

1.回文分割分析

对一个字符串按照回文进行分割,例如aba|b|bbabb|a|b|aba就是字符串ababbbabbababa的一个回文分割,每一个字串都是一个回文。请找到可以分割的最少的字串数。例如:

1.    ababbbabbababa最少4个字符串,分割三次:a|babbbab|b|ababa

2.    如果字符串整体是回文,则需要0次分割,最少1个字符串

分析:

我们可以先不理会最少分割,从对字符串进行回文分割入手,对于回文字符串的判断是不可避免的。我们可以从字符串的第一个字符开始,找满足回文的字 串。假设str[0..k]是一个回文字串(包括0和k),问题可以分解为子问题了:假设count[i]表示从i开始、包括i的字符串的回文切割数(当然是有很多可能的),如果str[0..k]是一个回文字符串,则count[i]=count[k+1] + 1。找到递归表达式,这里要注意,从0开始,可能会有多个回文子串,也正是因为如此,才有最少分割一说。

寻找最小分割数的时候,利用

cutNums = _minCut(s,i,k) + _minCut(s,k+1,j) + 1;

上面的算法中,每次要遍历一遍字符串,同时要判断子串是否是回文,这里显然是存在重复计算的。这给我们改进提供了可能。那么该如何更好的判断回文呢?我们设定P[i][j]:

1.    为true时,表示str[i..j]为回文

2.    为false时,表示str[i..j]不是回文

则,当:

1.    i==j时,P[i][j]=true

2.    j==i+1时,P[i][j]=str[i]==str[i]

3.    其他,P[i][j]=P[i+1][j-1]&&(str[i]==str[j])

这个P该如何构建呢?根据其状态转移的方程,P[i][j]所代表的字符串,长度从1开始变化,逐渐到整个字符串,是这样的一个构建的过程,所以外层循环应该是所要判断的字串的长度。基本代码如下:


回文字串判断完毕之后,改如何计算最少分割呢?我们可以根据P构建一棵树,然后宽度有限遍历,找到树的最小深度。上面判断回文的时间复杂度为 O(n^2),构建树的时间复杂度为遍历一次P,时间复杂度也是O(n^2),最后树的遍历,时间复杂度要小于O(n^2),这样,整体的时间复杂度为 O(n^2)。

也可以不这样考虑,如同我们上面说的,我们用C[i]表示str[0..j]的最小分割数,然后变量k从1开始到n遍历,找到最少的分割。时间复杂度O(n^2)。


2.今日面试题译数字串分析

翻译数字串,类似于电话号码翻译:给一个数字串,比如12259,映射到字母数组,比如,1 -> a, 2-> b,... , 12 -> l,... 26-> z。那么,12259 -> lyi 或 abbei 或 lbei 或 abyi。输入一个数字串,判断是否能转换成字符串,如果能,则打印所以有可能的转换成的字符串。动手写写吧。

分析

这个题目是一个比较直接,比较简单的题目。面试官会出这样的题目,一般都是考察大家的coding的功力的。这个实话实说,真的没有捷径。就是多写、多练,也可以阅读优秀的代码,不断的体会思路。

我们这里来分析一下这个题目的分析思路。看完这个描述,我们应该注意到一下的细节:

·         映射是在[1,26]这个范围内数字

·         输入的字符串是否包括0或者负数?

这些细节要注意,不明确的要咨询面试官,要不然,很容易让你的程序出现漏洞。面试官也比较在意这个交互的过程。

充分理解题目的含义、目的之后,很直接的就可以想到这个题目可以用递归解决。如原题中的例子:12259,它有两个递归的子问题,(1)2259和(12)259,前面的括号表示是否能够通过映射表翻译。同理每一个子问题,都会表示为这样的两个子问题。

接下来,我们考虑(1)2259的两个子问题:

1.    (12)259

2.    (122)59

大家有注意到,第一个和12259的一个子问题重复了。大家是否对这个似曾相识呢?当大家把递归过程的树形结构画出来,会发现更多的重复子问题,这就给了我们改进的空间,只需要取消这些重复计算就可以了。

第一个方法就是记忆法,将计算过的结果缓存起来,这样可以后续接着使用。但是更近一步,我们是可以采用动态规划的方法的。很多同学也都直接的想到了。

上面的过程,是为初学者指的路,希望能对大家有所帮助。但还有一个细节,大家要注意,这个题目不仅仅是判断是否可以,还需要打印出来所有的情况。这个细节要在编程的时候注意。


3.排列子串分析

给定两个字符串A和B,判断A中是否包含由B中字符重新排列成的新字符串。例如:A=abcdef, B=ba,结果应该返回true。因为ba的排列ab,是A的子串。

分析

我们假设A字符串的长度为m,B字符串的长度为n。首先,如果B的长度大于A了,则肯定返回的是false。所以,我们下面的讨论都是m>=n的情况。

最直接的,我们可以计算出B的所有排列字符串,然后逐个去A中匹配。B的所有排列字符串有n!个,每一个匹配,复杂度较高。

那么如何改进呢?是否可以减少匹配的次数呢?我们首先对B进行排序,采用快排,时间复杂度O(nlogn),得到D。然后在A中,从头开始,长度为 n的子串进行排序,然后判断与C与D是否相同。在A中,一共有m-n+1个长度为n的子串,每一个子串进行排序,时间复杂度为O(nlogn),总的时 间复杂度为O((m-n+1)nlogn),O(mnlogn)。但其实,在A中,只有第一个C的排序时O(nlogn)的,其他的都是去掉第一个字符, 然后插入下一个字符,排序时间复杂度为O(n),这样整体的时间复杂度为O(mn)。下面举个例子:

设定A=fedcba, B=ab,则m=6,n=2

1.    排序B,D=ab

2.    在A中,第一个长度为2的子串C=fe,排序之后为ef,与D不匹配,则从C中删除第一个字符f,加入下一个字符c,此时C=ec。再重复排序的过程。直到A遍历完毕。

明显第一个C需要快排,其他的只需要插入即可。

还有其他的方法,可以用hashmap存储B中的字符,和A中的每一个长度为n的字串进行判断是否匹配。


4.子串找分析

从一个长字符串中查找包含给定字符集合的最短子串。例如,长串为“aaaaaaaaaacbebbbbbdddddddcccccc”,字符集为 {abcd},那么最短子串是“acbebbbbbd”。如果将条件改为“包含且只包含给定字符集合”,你的算法和实现又将如何改动。

分析

本期的题目与上期的题目颇类似。上期我们在分析完之后,提到还可以利用hashmap来实现。这样可以进一步的减小时间复杂度。具体的思路与上期的方法也类似,只是在判断是否出现的时候,不是采用排序的方法,而是hashmap的方法。具体方法如下:

1.    遍历一边字符集合,将集合中的字符都加到hashmap中

2.    然后遍历长串,记录遍历开始pBegin和结束pEnd的位置,直到hashmap中的字符都出现过,则不断移动pBegin,查看是否能够满足所有字符集和中的字符都出现了,如果出现:则不断更新最小长度;如果没有出现,则停止移动pBegin,开始移动pEnd。依次类推,像一个可以伸缩的窗口在长串上遍历。

示例代码如下:

这个代码是《编程之美》中求解最短摘要的代码,其实本题,也是最短摘要的一个变化,字符集和就相当于查询了。

这里有一个小小的技巧,在移动pBegin的时候,怎么判断是否字符集合中的字符都出现了呢?可以采用给另外一个hashmap用来记录字符集中字符出现的次数,当pBegin指向的字符,出现在hashmap中,则其对应出现次数减1,如果次数为0了,则要停止pBegin,开始移动pEnd。

这个方法的时间复杂度为O(2*n + m),空间复杂度为O(n)。


5.词问题分析

给定字符串,以及一个字典,判断字符串是否能够拆分为字段中的单词。例如,字段为{hello,world},字符串为hellohelloworld,则可以拆分为hello,hello,world,都是字典中的单词。

分析

这个题目唤作“分词问题”,略显宽泛。只是想提及这个问题,这是在自然语言处理,搜索引擎等等领域中,非常基础的一个问题,解决的方法也比较多,相对比较成熟,不过这仍旧是一个值得进一步探索的问题。那我们先从这个简单的题目入手,看看如何处理题目中这个问题。 最直接的思路就是递归,很简单。我们考虑每一个前缀,是否在字典中?如果在,则递归处理剩下的字串,如果不在;则考虑其他前缀。示例代码如下:

在上面的代码中:每一种情况都要处理substr,程序的耗时比较长,如果在OJ上提交,干脆超时的,那么如何改进呢?

这个题目的处理,上期的题目是很相似的。在递归子问题中,找重复的子问题。也非常明显,如下图(图片来自GeeksforGeeks)所示:

所以,通过动态规划的方法,可以通过有较大幅度的提升,同样,这个题目与前面的每一个状态都有关系的,所以,是一个二重循环,时间复杂度为O(n^2)。示例代码如下:

除此之外,这个问题的方法是非常多的,大家还有什么思路呢?欢迎大家展开思路,在微博上互动讨论。

经过上一期的问题,还有这一期的,我想对于这类问题,递归以及动态规划方法的改进,童鞋们应该有比较好的体会了。希望大家能多做写联系,比如在OJ上,熟能生巧,提升代码功力。


6.最少入字符分析

给定字符串,可以通过插入字符,使其变为回文。求最少插入字符的数量。例如:

1.    ab最少插入1个字符,变为*b*ab

2.    aa最少插入0个字符

3.    abcd最少插入3个字符,*dcb*abcd

分析

这个题目的分析思路,和前面两期是非常相似的:给出递归的解法,发现重复的子问题,改进为动态规划的解法,这是一个分析的过程,待同学们比较熟悉时候,可以直接给出动态规划的解决方案,就很好了。

这个题目,递归该如何解呢?给定一个字符串str,长度为n,怎么插入最少的字符,是的字符串变为回文呢?插入最少的字符,就是要尽量利用原来的字符,在原字符串str中,尽量利用更多能够匹配的字符。怎么对这个问题进行分解呢?考虑str字符串整体:

1.    如果str[0]==str[n-1],则问题转变为求str[1,n-2],插入最少字符,得到回文

2.    如果str[0]!=str[n-1],则需要插入一个字符要么和str[0]相同,要么和str[n-1]相同,

a.    如果和str[0],则转变为str[1,n-1],插入最少字符,得到回文

b.    如果和str[n-1],则转变为str[0,n-2],插入最少字符,得到回文

上面的第2种情况中,需要取两个值最小值。则完成了问题的分解,并且,基本情况也分析完全,则有递归式为:

fmi(str, l, h) = (str[l] == str[h]) ? fmi(str, l+1, h-1) : (min(fmi(str, l+1, h), fmi(str,l, h-1))+1)

通过上面的式子,有经验的、熟练的同学,很直接的就能看出来,存在重复的子问题,这就意味着,我们可以讲子问题的解缓存使用。如果,没有直接能够看出来的同学们,还是可以按照我们之前的方法,把递归树画出来吧,那样更加一目了然。

那么,这个题目该如何用动态规划的解决呢?如何重复利用子问题的解呢?似乎有些不那么直接。但其实也是于规律可循的。上面的递归式,是从字符串的两 边,想中间移动递归,根据动态规划解决问题的思想,我们先解决子问题,再重复利用子问题,就是要从内向外解决,大家还记得回文子串判断的那个题目么,动态 规划解法的外层循环是子串的长度,这个题目也是类似的。示例代码如下:

这个题目在使用动态规划解的时候,略有点儿绕,不太好想到如何利用子问题的解。这个没有更好的方法,就是多多积累,遇到问题勤于思考,举一反三。

转载于:https://my.oschina.net/u/1412321/blog/183128

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值