• 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

  • 相关阅读:
    python:xlrd模块
    psql:转:会引起全表扫描的10种sql语句
    Linux相关
    面试题
    Siege Web服务器性能压力测试工具
    Nginx+uWSGI+Supervisor配置
    SQLAlchemy
    Virtualenv创建虚拟环境
    算法
    Mac常用快捷键
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247217.html
Copyright © 2020-2023  润新知