算法之---字符串循环移位

问题,给你一个字符串,要求循环左移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]开始再次进行移动,直到所有的元素都已经移动为止。Ln的最大公约数是所需的转换次数()

 

这里注意是第一轮移动是先移动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);

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值