您的位置:首页 > 职场人生

【程序员编程艺术】第一章:左旋转字符串

2014-06-20 18:13 417 查看
原文:http://blog.csdn.net/v_JULY_v/article/details/6322882

本文是在原文的基础上,加入自己的总结,可能会简略。毕竟这样才是属于自己的。

题目描述:

定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部,如把字符串abcdef左旋转2位得到字符串cdefab。

请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1)。

思路一:暴力移位法

这种方法,就跟删除数组中的元素一样,将i位置后面的元素依次向前移动一位,然后把要删掉的元素放到数组最后。针对长度为n的字符串来说,假设需要移动m个字符到字符串的尾部,那么总共需要 m*n 次操作,同时设立一个变量保存第一个字符,如此,时间复杂度为O(m
* n),空间复杂度为O(1),空间复杂度符合题目要求,但时间复杂度不符合,所以,我们得需要寻找其他更好的办法来降低时间复杂度。

void LeftShiftOne(char *s,int n)
{
char ch = s[0];
for(int i = 1;i < n;++i){
s[i-1] = s[i];
}
s[n-1] = ch;
}

void LeftShift(char *s,int n,int m)
{
while(m--)          //用m--更好,比重新定义一个变量i,然后再递增,更直观
LeftShiftOne(s,n);
}


思路二:指针反转法

先举出一个样例来分析:

样例1:9个字符,移动3个元素

abc defghi,若要让abc移动至最后的过程可以是:abc defghi->def abcghi->def ghiabc

如此,我们可定义俩指针,p1指向ch[0],p2指向ch[m];

一下过程循环m次,交换p1和p2所指元素,然后p1++, p2++;

第一步,交换abc 和def ,abc defghi->def abcghi
第二步,交换abc 和 ghi,def abcghi->def ghiabc

整个过程,看起来,就是abc 一步一步 向后移动

abc defghi
def abcghi
def ghi abc

//最后的 复杂度是O(m+n)

样例2:10个字符,移动3个元素

对abcdefghij序列进行左旋转操作:abcdefghij要变成defghij abc:

1. def abc ghij

2. def ghi abc j //接下来,j 步步前移

3. def ghi ab jc

4. def ghi a j bc

5. def ghi j abc

这是就需要处理最后剩余的长度小于要移动的m个元素。

那么如何处理最后的少于m个元素呢?

方案一:

可以像上图一样,一位一位移动;

//copyright@July、颜沙
//最终代码,July,updated again,2011.04.17。
#include <iostream>
#include <string>
using namespace std;

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 - m) - n % m;

// 交换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。
}

int main()
{
string ch="abcdefghijk";
rotate(ch,3);
cout<<ch<<endl;
return 0;
}


方案二:

也可以按照下面的方法,再接着交换,最后再移动:

def ghi abc jk

当p1指向a,p2指向j时,那么交换p1和p2,

此时为:

def ghi jbc ak

p1++,p2++,p1指向b,p2指向k,继续上面步骤得:

def ghi jkc ab

p1++,p2不动,p1指向c,p2指向b,p1和p2之间(cab)也就是尾巴,

那么处理尾巴(cab)需要循环左移一定次数(而后的具体操作步骤已在下述程序的注释中已详细给出)。

所以,解决问题的方法有很多,看你怎么去运用。

#include <iostream>
#include <string>
using namespace std;

//颜沙,思路二之方案二,
//July、updated,2011.04.16。
void rotate(string &str, int m)
{
if (str.length() == 0 || m < 0)
return;

//初始化p1,p2
int p1 = 0, p2 = m;
int n = str.length();

// 处理m大于n
if (m % n == 0)
return;

// 循环直至p2到达字符串末尾
while(true)
{
swap(str[p1], str[p2]);
p1++;
if (p2 < n - 1)
p2++;
else
break;
}

// 处理尾部,r为尾部循环左移次数
int r = m - n % m;  // r = 1.
while (r--)  //外循环执行一次
{
int i = p1;
char temp = str[p1];
while (i < p2)  //内循环执行俩次
{
str[i] = str[i+1];
i++;
}
str[p2] = temp;
}
//举一个例子
//abcdefghijk
//当执行到这里的时候,defghiabcjk
//      p1    p2
//defghi a b c j k,a 与 j交换,jbcak,然后,p1++,p2++
//        p1    p2
//       j b c a k,b 与 k交换,jkcab,然后,p1++,p2不动,

//r = m - n % m= 3-11%3=1,即循环移位1次。
//          p1  p2
//       j k c a b
//p1所指元素c实现保存在temp里,
//然后执行此条语句:str[i] = str[i+1]; 即a跑到c的位置处,a_b
//i++,再次执行:str[i] = str[i+1],ab_
//最后,保存好的c 填入,为abc,所以,最终序列为defghi jk abc。
//July、updated,2011.04.17晚,送走了她。
}

int main()
{
string ch="abcdefghijk";
rotate(ch,3);
cout<<ch<<endl;
return 0;
}


思路三:递归转换法

比如下面的例子

1、对于字符串abc def ghi gk,

将abc右移到def ghi gk后面,此时n = 11,m = 3,m’ = n % m = 2;

abc def ghi gk -> def ghi abc gk

2、问题变成gk左移到abc前面,此时n = m’ + m = 5,m = 2,m’ = n % m 1;

abc gk -> a gk bc

3、问题变成a右移到gk后面,此时n = m’ + m = 3,m = 1,m’ = n % m = 0;

a gk bc-> gk a bc。 由于此刻,n % m = 0,满足结束条件,返回结果。
设字符串总长度为L,左侧要旋转的部分长度为s1,那么当从左向右循环交换长度为s1的小段,直到最后,由于剩余的部分长度为s2(s2==L%s1)而不能直接交换。该问题可以递归转化成规模为s1+s2的,方向相反(从右向左)的同一个问题。随着递归的进行,左右反复回荡,直到某一次满足条件L%s1==0而交换结束。
利用递归的思想来解决问题。代码略

思路四、循环移位法(比较难,略)

思路五:三步翻转法

这道题可以在《剑指offer》上看到。

对于这个问题,换一个角度思考一下。

将一个字符串分成X和Y两个部分,在每部分字符串上定义反转操作,如X^T,即把X的所有字符反转(如,X="abc",那么X^T="cba"),那么就得到下面的结论:(X^TY^T)^T=YX,显然就解决了字符串的反转问题。

例如,字符串 abcdef ,若要让def翻转到abc的前头,只要按照下述3个步骤操作即可:

首先将原字符串分为两个部分,即X:abc,Y:def;
将X反转,X->X^T,即得:abc->cba;将Y反转,Y->Y^T,即得:def->fed。
反转上述步骤得到的结果字符串X^TY^T,即反转字符串cbafed的两部分(cba和fed)给予反转,cbafed得到defabc,形式化表示为(X^TY^T)^T=YX,这就实现了整个反转。

如下图所示:



void ReverseString(char* s,int from,int to)
{
while (from < to)
{
char t = s[from];
s[from++] = s[to];
s[to--] = t;
}
}

void LeftRotateString(char* s,int n,int m)
{
m %= n;               //若要左移动大于n位,那么和%n 是等价的
ReverseString(s, 0, m - 1); //反转[0..m - 1],套用到上面举的例子中,就是X->X^T,即 abc->cba
ReverseString(s, m, n - 1); //反转[m..n - 1],例如Y->Y^T,即 def->fed
ReverseString(s, 0, n - 1); //反转[0..n - 1],即如整个反转,(X^TY^T)^T=YX,即 cbafed->defabc。
}


扩展:

1、链表翻转。给出一个链表和一个数k,比如,链表为1→2→3→4→5→6,k=2,则翻转后2→1→6→5→4→3,若k=3,翻转后3→2→1→6→5→4,若k=4,翻转后4→3→2→1→6→5,用程序实现。

2、编写程序,在原字符串中把字符串尾部的m个字符移动到字符串的头部,要求:长度为n的字符串操作时间复杂度为O(n),空间复杂度为O(1)。 例如,原字符串为”Ilovebaofeng”,m=7,输出结果为:”baofengIlove”。

3、单词翻转。输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。例如,输入“I am a student.”,则输出“student. a am I”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: