左旋转字符串

来源:互联网 发布:无限流量破解软件 编辑:程序博客网 时间:2024/04/30 03:46

题目:字符串的左旋转操作:把字符串前面的若干字符移到字符串的后面。例如:字符串abcdefg左旋转2位得到cdefgab。请实现左旋转字符串函数,要求对于长度为n的字符串,时间复杂度为O(n),辅助内存为O(1)。

分析1:看到这道题我们最直观的思路就是:如果要把长度为n的字符串左移k位,那么我们可以动态分配长度为k的临时数组,存储前面k个字符,然后将后面的字符逐一向前移动k位,然后将临时数组中的字符拷贝到后面k位。但是这种方法使用的k个额外位置产生了过大的存储空间的消耗。

分析2:我们也可以定义一个子函数,该函数的功能是将一个字符串左旋转1位(其时间正比于n),然后调用该函数k次。但是该方法又产生了过多的时间消耗。

分析3:要在题目限定的范围内解决该问题,我们有一个著名的“杂技算法”:移动S[0]到临时变量t,然后移动S[K]到S[0];移动S[2K]到S[K],依此类推(将S中的所有下标对n取模),直到返回到取S[0]中的元素,此时改为从t取值然终止过程。当n为12,k为3时,元素按如下顺序移动(如下图,画得太丑了,莫怪)。如果该过程没有移动全部的元素,则从S[1]开始再次移动,直到所有的元素都已经移动位置。

根据上面的思路,我们可以写出如下的代码:

复制代码
 1 #include<iostream> 2 #include<string> 3 using namespace std; 4  5 //求两个整数i,j的最大公约数 6 int gcd(int i,int j) 7 { 8     while(i != j) 9     {10         if(i > j)11             i -= j;12         else13             j -= i;14     }15     return i;16 }17 18 19 //左旋转字符串函数,将pStr字符串向左旋转k位,20 //first指向pStr[i],second指向pStr[2i],以此类推21 char* LeftRotateString(char* pStr,int k)22 {23     if(pStr == NULL || k < 1)24         return 0;25 26     int n = strlen(pStr);27     28     //共进行i趟循环,i为n,k的最大公约数29     for(int i = 0;i < gcd(n,k);++i)30     {31         char temp = pStr[i];32         int first = i;33         while(1)34         {35             int second = (first + k) % n;36 37             if(second == i)38                 break;39             40             pStr[first] = pStr[second];41             first = second;42         }43 44         //将临时变量中的字符存至每一趟的循环的最后一个字符中45         pStr[first] = temp;46     }47     return pStr;48 }49  50 int main()51 {52     string demo("abcdefgh");53     cout<<demo<<endl;54 55     LeftRotateString(&demo[0],3);56 57     cout<<demo<<endl;58     return 0;59 }
复制代码

运行结果如下:

效率分析:很显然这个算法的空间复杂度是O(1),看似是for循环中嵌套了while循环,然而仔细想想不难发现,这个算法实际上只遍历了一个数组,只不过采取的遍历方式是“跨越式”访问的,所以它的时间复杂度是O(n),满足题目的要求,是一个比较好的算法了。

 

分析4:我们可以将该问题看成是数组ab转换成ba,同时假定我们有一个子函数可以将数组中的所有元素求逆序。显然有(a^r)^r=a,即对一个数组a求逆再求逆等于它本身。所以我们从ab开始,首先对a求逆,得到a^r,然后对b求逆得到b^r。最后整体求逆,得到(a^r*b^r)^r=ba。这正是我们要的结果!

于是我们有如下代码:

复制代码
 1 #include<string> 2 #include<iostream> 3 using namespace std; 4  5 //对一个数组中的所有元素求逆,即把abcde变为edcba 6 void ReverseString(char *pStart,char *pEnd) 7 { 8         if(pStart != NULL && pEnd != NULL) 9         {10                 while(pStart <= pEnd)11                 {12                     char temp = *pStart;13                     *pStart = *pEnd;14                     *pEnd = temp;15 16                     ++pStart;17                     --pEnd;18 19                 }20         }21 }22 23 24 //把字符串pStr左旋转k位25 char* LeftRotateString(char* pStr, int k)26 {27         if(pStr != NULL && k > 0)28         {29                 int n = strlen(pStr);30                 if(n > 0 && k < n)31                 {32                         char* pFirstStart = pStr;33                         char* pFirstEnd = pStr+k-1;34                         char* pSecondStart = pStr+k;35                         char* pSecondEnd = pStr+n-1;36 37                         //求第一部分字符串的逆,即求a^r38                         ReverseString(pFirstStart,pFirstEnd);39                         //求第二部分字符串的逆, 即求b^r40                         ReverseString(pSecondStart,pSecondEnd);41                         //求前两部分整体的逆,  即求(a^r*b^r)^r42                         ReverseString(pFirstStart,pSecondEnd);43                 }44         }45 46         return pStr;47 }48 49 50 int main()51 {52         string demo("abcdefgh");53         cout<<demo<<endl;54 55         LeftRotateString(&demo[0],3);56 57         cout<<demo<<endl;58         return 0;59 60 }
复制代码

运行结果如下:

效率分析:“翻转代码在时间和空间上效率都很高,而且代码非常的简短,很难出错”(出自《编程珠玑》)。实际上由于要进行ab的求逆运算,对a,b分别求逆的时候相当于遍历一遍数组,而对整体求逆的时候又要遍历一边数组,这样整体看来对于长度为n的字符串需要遍历两遍,因此时间复杂度为O(n)。但是翻转代码不需要额外分配新的内存,一个额外的空间都不需要。

 

参考文献:

【1】Jon Bentley.  《编程珠玑(第二版)》.  人民邮电出版社,2008      p13-p15

【2】程序员面试题精选100题:http://zhedahht.blog.163.com/blog/static/2541117420073993725873/

 

注:

1)本博客所有的代码环境编译均为win7+VC6。所有代码均经过博主上机调试。

2)博主python27对本博客文章享有版权,网络转载请注明出处http://www.cnblogs.com/python27/。对解题思路有任何建议,欢迎在评论中告知。

 

原创粉丝点击