问题叙述:
将一个n元一维向量向左旋转i个位置。例如,当n=8且i=3时,"abcdefgh"旋转为"defghabc",要求时间为O(n),额外存储占用为O(1)。(《编程珠玑》第二章问题B)
分析:
严格来说这并不是一个字符串,因为' '是不会移动的。为了叙述方便,可以把它认为是字符串,只是不对' '进行操作罢了。
如果不考虑时间要求为O(n),那么可以每次整体左移一位,一共移动i次。只使用O(1)的空间的条件下,一共要进行元素交换O(n*i)次;
如果不考虑空间要求为O(1),那么可以把前i个存入临时数组,剩下的左移i位,再把临时数组里的内容放入后i个位置中。
很可惜,由于两个限制条件,以上两种思路都不满足要求。
对于算法1和算法2,如果理解有困难,不必强求,能掌握算法3就好。
为了满足O(1)空间的限制,延续第一个思路,如果每次直接把原向量的一个元素移动到目标向量中它的应该出现新位置上就行了。先把array[0]保存起来,然后把array[i]移动到array[0]上,array[2i]移到array[i]上,直至返回取原先的array[0]。但这需要解决的问题是,如何保证所有元素都被移动过了?数学上的结论是,依次以array[0],...,array[gcd(i,n)-1]为首元进行循环即可,其中gcd(a,b)是a与b的最大公约数。因此算法可写为:
int vec_rotate(char *vec,int rotdist, int length) { int i,j,k,times; char t; times = gcd(rotdist,length); printf("%d ",times); for(i=0;i<times;i++) { t = vec[i]; j = i; while(1) { k = j+ rotdist; if(k>=length) k-=length; if(k==i) break; vec[j] = vec[k]; j = k; } vec[j]=t; } return 0; }
正如“杂技”一词所暗示的一样,这个算法就像在玩杂耍球,你要让它们中的每一个都在合适的位置上,这些球,除了手中有一个,其它几个都在空中。如果不熟悉,很容易手忙脚乱,把球掉的满地都是。
考虑第二个思路,相当于把向量x分为两部分a和b,左移就是把ab变成ba,其中a包含了前i个元素。假设a比b短,把b分为bl和br,那么需要先把a与br交换得到brbla,再对brbl递归左旋。而a比b长的情况类似,当a与b长度相等时直接两两交换元素就能完成。同时可以看到,每次待交换的向量长度都小于上一次,最终递归结束。
int vec_rotate_v1(char *vec,int i, int length) { int j = length - i; if(i>length) i = i%length; if(j==0) return 0; else if(j>i) { //case1: ab -> a-b(l)-b(r) swap(vec,0,j,i); vec_rotate_v1(vec,i,j); } //case2: ab -> a(l)-a(r)-b //i becomes less else if(j<i) { swap(vec,i,2*i-length,j); vec_rotate_v1(vec,2*i-length,i); } else swap(vec,0,i,i); return 0; } int swap(char* vec,int p1,int p2, int n) { char temp; while(n>0) { temp = vec[p1]; vec[p1] = vec[p2]; vec[p2] = temp; p1++; p2++; n--; } return 0; }
这个算法的缺陷是,需要对三种情况进行讨论,而且下标稍不注意就会出错。
延续算法2的思路,并假定有一个辅助函数能对向量求逆。这样,分别对a、b求逆得到arbr,再对整体求逆便获得了ba!难怪作者也要称之为“灵光一闪”的算法。可能以前接触过矩阵运算的读者对此很快便能理解,因为(ATBT)T = BA。算法如下:
int vec_rotate_v2(char *vec,int i,int length){ assert((i<=0)||(length<=0)) if(i>length) i = i%length; if (i==length) { printf("i equals n. DO NOTHING. "); return 0; } reverse(vec,0,i-1); reverse(vec,i,length-1); reverse(vec,0,length-1); return 1; } int reverse(char *vec,int first,int last){ char temp; while(first<last){ temp = vec[first]; vec[first] = vec[last]; vec[last] = temp; first++; last--; } return 0; }
如果能想到,算法3无疑既高效,也难以在编写时出错。有人曾主张把这个求逆的左旋方法当做一种常识。
来看看这种思想的应用吧:
扩展:(google面试题)用线性时间和常数附加空间将一篇文章的所有单词倒序。
举个例子:This is a paragraph for test
处理后: test for paragraph a is This
如果使用求逆的方式,先把全文整体求逆,再根据空格对每个单词内部求逆,是不是很简单?另外淘宝今年的实习生笔试有道题是类似的,处理的对象规模比这个扩展中的“一篇文章”小不少,当然解法是基本一样的,只不过分隔符不是空格而已,这里就不重述了。
作者:五岳
出处:http://www.cnblogs.com/wuyuegb2312
对于标题未标注为“转载”的文章均为原创,其版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。