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]开始再次移动,直到所有的元素都已经移动位置。
![](http://pic002.cnblogs.com/images/2011/353956/2011112814042467.png)
根据上面的思路,我们可以写出如下的代码:
运行结果如下:
![](http://pic002.cnblogs.com/images/2011/353956/2011112814582073.png)
效率分析:很显然这个算法的空间复杂度是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。这正是我们要的结果!
于是我们有如下代码:
运行结果如下:
![](http://pic002.cnblogs.com/images/2011/353956/2011112815190498.png)
效率分析:“翻转代码在时间和空间上效率都很高,而且代码非常的简短,很难出错”(出自《编程珠玑》)。实际上由于要进行ab的求逆运算,对a,b分别求逆的时候相当于遍历一遍数组,而对整体求逆的时候又要遍历一边数组,这样整体看来对于长度为n的字符串需要遍历两遍,因此时间复杂度为O(n)。但是翻转代码不需要额外分配新的内存,一个额外的空间都不需要。
分析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]开始再次移动,直到所有的元素都已经移动位置。
![](http://pic002.cnblogs.com/images/2011/353956/2011112814042467.png)
根据上面的思路,我们可以写出如下的代码:
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 }
运行结果如下:
![](http://pic002.cnblogs.com/images/2011/353956/2011112814582073.png)
效率分析:很显然这个算法的空间复杂度是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 }
运行结果如下:
![](http://pic002.cnblogs.com/images/2011/353956/2011112815190498.png)
效率分析:“翻转代码在时间和空间上效率都很高,而且代码非常的简短,很难出错”(出自《编程珠玑》)。实际上由于要进行ab的求逆运算,对a,b分别求逆的时候相当于遍历一遍数组,而对整体求逆的时候又要遍历一边数组,这样整体看来对于长度为n的字符串需要遍历两遍,因此时间复杂度为O(n)。但是翻转代码不需要额外分配新的内存,一个额外的空间都不需要。
相关文章推荐