您的位置:首页 > 其它

04 左旋转字符串

2014-05-15 15:07 134 查看
题目:字符串的左旋转操作:把字符串前面的若干字符移到字符串的后面。例如:字符串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         else
13             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^r
38                         ReverseString(pFirstStart,pFirstEnd);
39                         //求第二部分字符串的逆, 即求b^r
40                         ReverseString(pSecondStart,pSecondEnd);
41                         //求前两部分整体的逆,  即求(a^r*b^r)^r
42                         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)。但是翻转代码不需要额外分配新的内存,一个额外的空间都不需要。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: