01左旋字符串

题目描述:

        定义字符串的左旋转操作:把字符串前面的k个字符移动到字符串的尾部。如把字符串abcdef左旋转k=2位得到字符串cdefab。请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1) 

 

        大家开始可能会有这样的潜在假设,K<N。事实上,很多时候也的确是这样的。但严格来说,我们不能用这样的“惯性思维”来思考问题。尤其在编程的时候,全面地考虑问题是很重要的,K可能是一个远大于N的整数。

        仔细观察循环左移的特点,不难发现:每个元素左移N位后都会回到自己的位置上。因此,左移K位之后的情形,跟左移K’= K % N位之后的情形一样。

 

        有三种方法可以实现线性时间内的左旋操作:

1、三次翻转,直接线性

2、两个指针逐步翻转,线性

3、stl的rotate算法,线性

 

1:三次翻转

        将一个字符串分成两部分,X和Y两个部分,在字符串上定义反转的操作: ,即把X的所有字符反转(如,X="abc",那么 ="cba"),那么我们可以得到下面的结论:

 = YX。这就可以转化为字符串的反转的问题了。就拿abcdef 这个例子来说,若要让def翻转到abc的前头,那么只要按下述3个步骤操作即可:

        a、首先分为俩部分,X: abc,Y: def;

        b、X-> ,abc->cba,Y-> ,def->fed;

        c、 = YX,cbafed->defabc,即整个翻转。

 

    代码如下:

char *invert(char *start, char *end) 

{    

    char  tmp, *ptmp = start;     

    while (start != NULL && end != NULL && start < end)   

    {    

        tmp = *start;    

        *start = *end;       

        *end = tmp;      

        start ++;    

        end --;  

    } 

    return ptmp; 

 

char *leftrotate(char *s, int pos)           //pos为要旋转的字符个数,或长度。 

    int len = strlen(s); 

    invert(s, s + (pos - 1));             //即 abc->cba 

    invert(s + pos, s + (len - 1));    //即 def->fed 

    invert(s, s + (len - 1));              //即cbafed->defabc。 

    return s; 

}

 

2、两个指针逐步翻转

        先来看个例子,如下

abcdefghi,若要abc移动至最后:abcdef ghi->def abc ghi->def ghi abc

        设m表示左旋的字符个数,n表示字符串长度。所以可定义俩指针,p1指向ch[0],p2指向ch[m],交换p1和p2所指元素,然后p1++, p2++。

        但是上面的例子是一种特殊情况,更普遍的情况是最后一次交换时会留有尾巴,比如如果abc def ghi j要变成def ghi j abc:
  abc def ghi j -> def abc ghi j-> def ghi abc j

        此时abc j就是最后的尾巴,已经不能按照前面的处理方法了,所以需要如下的步骤: def ghi abc j -> def ghi abjc-> def ghi aj bc-> def ghi j abc。

 

总结如下:

        a、首先让p1=ch[0],p2=ch[m],即让p1,p2相隔m的距离;

        b、判断p2+m-1是否越界,如果没有越界转到c,否则转到d

        c、不断交换*p1与*p2,然后p1++,p2++,循环m次,然后转到2。

        d、此时p2+m-1已经越界,在此只需处理尾巴。过程如下:

           d.1 通过n-p2得到p2与尾部之间元素个数r,即我们要前移的元素个数。

           d.2 以下过程执行r次:

             ch[p2]<->ch[p2-1],ch[p2-1]<->ch[p2-2],....,ch[p1+1]<->ch[p1];p1++;p2++;

 

代码如下:

void  rotate(string &str, int m) 

    if (str.length() == 0 || m <= 0) 

        return; 

     

    int n = str.length(); 

     

    if (m % n <= 0) 

        return; 

     

    int p1 = 0, p2 = m; 

    int k = n - n % m - m;  //k表示尾巴之前的字符个数 

     

    // 交换p1,p2指向的元素,然后移动p1,p2 

    while (k --)  

    { 

        swap(str[p1], str[p2]); 

        p1++; 

        p2++; 

    } 

     

    // 重点,都在下述几行。处理尾巴,r为尾部左移次数 

    int r = n - p2; 

    while (r--) 

    { 

        int i = p2; 

        while (i > p1) 

        { 

            swap(str[i], str[i-1]); 

            i--; 

        } 

        p2++; 

        p1++; 

    } 

    //比如一个例子,abcdefghijk 

    //                                                 p1                       p2 

    //当执行到这里时,defghi          a b c                      j    k 

    //p2+m出界了,r=n-p2=2,所以以下过程,要执行循环俩次。 

    //第一次:j 步步前移,abcjk->abjck->ajbck->jabck 

    //然后,p1++,p2++,p1指a,p2指k。 

    //                                        p1                     p2 

    //第二次:defghi j              a b c                  k 

    //同理,此后,k步步前移,abck->abkc->akbc->kabc。 

         这里的尾巴的处理,其实可以采用递归的方法,递归的进行左旋或者右旋即可,不再赘述。

   

3:stl的rotate算法

        考虑下面两个例子:

        a: 将abcdef左旋2位,所以m=2, n=6,所以可以这样模拟:abcdef|ab

        从第0位开始:0 <- 2 <- 4 <- 0(6%n == 0),得到cbedaf。接下来从第1位开始:1 <- 3 <- 5 <- 1(7%n == 1),得到cdefab。此时,从0到5都已经移动了一次,所以得到了最终结果。

 

        b:将abcdefg左移2位,所以m=2, n=7,所以可以这样模拟:abcdefg|ab

        从第0位开始:0 <- 2 <- 4 <- 6 <- 1(8%n == 1) <- 3 <- 5 <-0(7%n == 0)。至此,从0到6都已经移动了一次,所以得到了最终结果。

 

        以上就是stl中的rotate算法的思想,具体描述如下:根据循环链来进行移动,所谓的循环链,就是所有序号为(j+ i*m) % n的元素构成的“环”,j为循环链的开始序号。循环链上的所有元素都移动到循环链中的前一位置。

        一个循环链上共有多少个元素?从j开始,下一个元素是(j+m)%n,然后依次是(j+2m)%n, (j+3m)%n,..., (j+km)%n。所谓的循环链,就是因为链中所有序号构成了环,所以j = (j+km)%n。k就是使该等式成立的最小值。那么k等于多少,循环链上就有多少个元素。

        j = (j+km)%n,意味着n|km (|表示整除),设t = gcd(n, m)表示n和m的最大公约数,所以n = xt,  m = yt,因为t是最大公约数,所以x和y互质。所以: xt|kyt -> x|ky,因为x,y互质,所以k = x = n/t。所以,循环链中的元素个数为。所以,一共有gcd(n, m)个循环链。

        这样,每个循环链上的元素移动一次,所有循环链完成移动之后,也就达到了左旋的效果。

 

        gcd是求最大公约数的算法:给定俩个正整数m,n(m>=n),求它们的最大公约数。(注意,一般要求m>=n,若m<n,则要先交换m<->n。下文,会具体解释)。

        用数学定理表示即为:“定理:gcd(a,b) = gcd(b, a % b) (a>b 且a % b 不为0)”。以下,是此算法的具体流程:

a、[求余数],令r=m%n,r为n除m所得余数(0<=r<n);

b、[余数为0?],若r=0,算法结束,此刻,n即为所求答案,否则,继续,转到3;

c、[重置],置m<-n,n<-r,返回步骤1.

 

代码如下:

void  my_rotate(char *begin, char *mid, char *end)   

{      

    int n = end - begin;          //n表示字符串长度

    int k = mid - begin;          //k表示左旋元素的个数   

    int d = gcd(n, k);               //d表示循环链个数

    int i, j;      

    for (i = 0; i < d; i ++)                  //i表示循环链的起始序号

    {      

        int tmp = begin[i];      

        int last = i;      

           

        //j为循环链的下一个元素

        for (j = (i + k) % n; j != i; j = (j +k) % n)        

        {      

            begin[last] = begin[j];          

            last = j;      

        }          

        begin[last] = tmp;      

    }      

}    




http://blog.csdn.net/v_JULY_v/article/details/6322882

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值