关于字符串左移的解法


   关于这个问题,已经有很多牛人都讨论过了,而且我的整理也是来自于July大神的这篇博客 

http://blog.csdn.net/v_july_v/article/details/6322882 。 

    我的这篇博客也算是站在巨人的肩膀上吧,不能算盗版吧!写这篇博文有两点原因,一是因为原博文看起来有点繁杂,如果是没有耐心的人估计难以坚持看完。事实上我也是结合自己的想法跳着看完了,没有看代码,这是我自己一贯的思路。代码是实现的细节,只要算法的思路清晰了,那么代码的实现是没有问题的。所以秉承这个思路,我的这篇博文不会贴上代码,从头至尾梳理算法的思路,不过不保证以后更新的时候不会加上代码。另外一个原因便是该问题确实比较值得思考,不同的思路,不同的做法,很有思考的意义。基于上面两点,写下了这篇博文。


问题定义

    一个长度为n的字符串,我们要循环左移m位。例如abcde,循环左移2位的结果就是cdeab。要求在O(n)的时间复杂度和O(1)的空间复杂度内解决该问题。

    对于一个长度为n的串,如果我们左移n位,结果就是原串,因此我们的m = m%n,对于这一点是没有问题的。其次如果我们面对的是右移m`位(m`<n)怎么办,那么我们对应的就是左移n-m`位,这个也很容易知道为什么是对的,在此便不加解释。那么下面我们就来全神贯注的思考如何解决一个字符串左移问题。


使用字符串翻转的办法

    很明显我们可以知道如果将abcde左移2位得到的结果便是cdeab,其实我们可以看到就是相当于将前2位直接搬到了原串的后面。

    于是我们很容易想到先将abcde整体翻转得到edcba,然后将后面的m位单独翻转,得到edcab,再将前面的(n-m)位进行翻转得到cdeab。于是很清晰的得到了翻转解决该问题的办法。

    对于一个给定的串进行反转的程序,我们姑且认为是O(1)的空间复杂度,那么整个办法的时间复杂度就是严格的O(2n),我想这个办法已经很不错了吧。


递归解决该问题

    同样举abcde左移2位的这个例子,结果就是cdeab,我们发现只是将ab作为整体移到了串的末尾。因此我们尝试直接先将前m位直接移动到目标位置得到decab,我们发现前面的3位便是dec,只需要左移两位便是正确的结果。因此不难发现该问题可以用递归解决。

    另外我们注意,当m>(n-m)的时候,例如abcde左移3位,我们先移动前面2位到达目标位置得到decab,显然我们只需要将后面的三位左移1位就行了。因此我们知道在m>(n-m)的时候,我们将前面的(n-m)位同后面的(n-m)位交换,然后将串的后m位左移(m-(n-m))位即可。

    事实上,上面两个情况是一样的,都是将串的前m位同串末尾的m位进行对应的交换,只不过因为实际情况的不同分为上面的两个走向。

    递归的方法应该说达到了严格的O(n)的复杂度,但是我们知道,递归是要系统自动维护栈的,因此对于这种解法所消耗的资源我们存保留意见。


利用数学解决该问题

    其实对于这道题,最初一看的想法就是将当前位依次替换左移m位对应的那个位,然后依次替换。后来发现有的情况一次循环替换就能全部完成整个串的左移,而有的情况下会出现多个循环链,一时只得到规律,不能想到很好的证明办法,只怪以前初等数论没有好好学啊!

    我们发现对于长度为n的串,左移m位,会形成gcd(n, m)个链,这里的gcd就是大家熟知的最大公约数,每个链的长度显然就是n/(gcd(n, m))。我们如何来证明这个问题呢?我们令i+j*m 和 i+k*m正好走了一圈又指向同一个元素,那么便是(i+j*m)%n == (i+k*m)%n,因此根据同余的性质我们知道n | (k-j)*m,而n = n' * gcd(n, m), m = m' * gcd(n, m)。所以我们得到n' | (k-j) * m', 由于n' 和 m' 互素,因此 n' | (k - j),因此我们知道最小的k-j = n' = n/gcd(n, m),也就是说一个循环链里头有n/gcd(n, m)个元素,因此总共有gcd(n, m)个循环链,于是得证。因此我们只需要取前gcd(n, m)个元素,每个走一条链进行循环的替换就行了。其实这个算法是最优的,真正的O(N)的时间复杂度,而且没有额外的开销。


后记

    你可能发现了,我在这里并没有提及前面连接博客中提到的双指针的做法,因为那种做法始终逃不过最坏情况是O(n^2)的悲剧命运。就举个例子说一个长度为2k-1的串,左移k位,那么按照那里的思路,我们需要将后面的(k-1)位逐个向前移动k位,显然时间复杂度就是k*(k-1),这里的k就是n/2,所以近似复杂度就是O(n^2)的。我们看为什么会这样子,因为那个方法最后在剩下的字符数小于m的时候,要逐个的移动每个字符m次,这样一下子提高了算法的复杂度。

    其实上面提到的三种思路已经很优了,解决一道题能想到三种办法已经很好了,在这里对July表示一下仰慕。我在这里只是对原博客中的内容进行一下概括性的综述,如果想要看代码和更详细内容的读者可以前往开头的链接博客进行学习。

    另外若是读者发现该文中有问题,希望牛人不吝赐教,我当认真倾听教诲。

    最后祝各位元旦快乐,新年新气象。






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值