问题,给你一个字符串,要求循环左移n位
比如对"abcdefg"循环左移2位,我们要得到"cdefgab"
(不同的考官可能对程序的具体要求不同,这里要求时间复杂度O(n),空间复杂度为O(1),再加附加条件,诸如不能使用库函数,不能使用连续辅助空间(包括动态分配),只能使用若干单个变量即O(1)空间,这里的n指字符串的长度)
解答:字符串的循环移位有如下三种算法思路:
第一种---最简单的:
将移动n位看做“每次移动一位,共操作n次”,这是一种化整为零的思维方法。只要能想到这一步,相信下面的代码就不难写出了:
void shift_1(char* str)
{
int len = strlen(str);
if(len <= 1)
return;
char temp = str[len-1];
for(int i=len-1; i>= 1; i--)
str[i] = str[i-1];
str[0] = temp;
}
void Shift1(char* a, int m)
{
if(! a)
return;
for(int i=1; i<= m; i++)
shift_1(a);
}
很显然这个算法的时间复杂度为O(n*l),空间复杂度为O(1)。其中l为字符串长度。
第二种----一次到位:
直接采用一次交换的思想,先移动x[0]到临时变量t,然后移动x[n]到x[0],x[2n]至x[n],依此类推(将x中的所有下标对L取模, L为字符串的长度,n为要移动的位数),直至返回到取x[0]中的元素,此时改为从t取值然后终止过程。如果该过程没有移动全部元素,就从x[1]开始再次进行移动,直到所有的元素都已经移动为止。L和n的最大公约数是所需的转换次数()。
这里注意是第一轮移动是先移动x[0]到临时变量t,移动x[n]到x[0],x[n+n]至x[n]...这里是等差数列不是等比数列哦 直至xn%L==0停止此时将t移动到x[0]
所以第二轮移动是先移动x[1]到临时变量t,移动x[1+n]到x[1],x[1+2n]至x[n+1]...
直至(1+xn)%L==1停止此时将t移动到x[1]....
依次类推。
程序代码如下:
//求最大公约数
int gcd(int i,int j)
{
while(i != j )
{
if(i > j)
i = i - j;
else
j = j - i;
}
return i;
}
void Shift2(char *a,int m,int n)//n为字符串的长度,m为要移动的位数
{
int gcdnum = gcd(m,n);//求m,n的最大公约数
int i,j,k,t;
for(j=0;j<gcdnum;j++)
{
t = a[j];
i=j;
while(1)
{
k =(m+i) % n;
if (k == j)
break;
a[i] = a[k];
i = k;
}
a[i] = t;
}
}
该算法的时间复杂度取决于外层循环执行的次数,当n,L的最大公约数为1,则外层循环执行一次,如果最大公约数为k,则执行k次,n可能等于L,即k可能等于L,所以其时间复杂度几乎也为O(L)>O(n)。空间复杂度较低,只占用一个字符的额外空间,为O(1)。
第三种---倒序再倒序思想:
原理:我们知道,反转一个字符串操作("abcd"变"dcba"),是不需要额外数组辅助的,只要头尾数据交换就可以了。这儿我们利用多次反转字符串来进行字符串的循环移动.
观察移位后的字符串和原串的差别,字符串移n位相当于把前n位挪到了后面,而把后n位移到前面。设字符串长为L,把字符串分成2个部分,前(L-n)个字符看做子串A,后n个字符看做子串B,则字符串可用AB表示。完成移位操作后新串为BA。那么移位的过程就可以看做AB到BA的过程。
用A'表示A的反序串,B'表示B的反序串,那么 BA = B''A'' =(A'B')'。
代码如下:
//将a中从start到end翻转
void reverse(char *a,int start, int end)
{
int i ,j,temp;
for(i = start,j = end; i < j; i++,j--)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//a为原数据,m为要左翼的位数,n为总长度
void Shift3(char *a,int m,int n) //这是循环左移
{
int left = m % n;//这样即使m>n也无妨
if(left == 0)
return ;
reverse(a,0,left-1);
reverse(a,left,n-1);
reverse(a,0,n-1);
return ;
}
算法的空间复杂度为O(1)。把赋值操作作为考量时间复杂度的基本单位,3次调用reverseOrder函数总共执行赋值操作3L,则算法的时间复杂度为O(L)。
如果不考虑空间和时间复杂度,而且可以使用库函数的话,直接用strcpy函数最简单了。
代码如下:
void left_shift(char* str,int n)
{
int len=strlen(str);
char *arr=(char*)malloc(sizeof(char)*len+1);
printf("%s\n",str);
strncpy(arr,str+n,len-n);//将[n,len)区间的字符左移n个位置
printf("%s\n",arr);
strncpy(arr+len-n,str,n);//将[0,n)区间的字符右移len-n个位置
cout<<"arr="<<arr<<endl;
strncpy(str,arr,len);//将移动好的字符复制回原字符串
free(arr);
}
void right_shift(char* str,int n)
{
int len=strlen(str);
char *arr=(char*)malloc(sizeof(char)*len+1);
printf("%s\n",str);
strncpy(arr,str+len-n,n);//将[0,len-n)区间的字符右移n个位置
printf("%s\n",arr);
strncpy(arr+n,str,len-n);//将[n,len)区间的字符左移len-n个位置
printf("%s\n",arr);
strncpy(str,arr,len);//将移动好的字符复制回原字符串
free(arr);
}