【程序员编程艺术】第一章:左旋转字符串
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),空间复杂度符合题目要求,但时间复杂度不符合,所以,我们得需要寻找其他更好的办法来降低时间复杂度。
思路二:指针反转法
先举出一个样例来分析:
样例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个元素呢?
方案一:
可以像上图一样,一位一位移动;
方案二:
也可以按照下面的方法,再接着交换,最后再移动:
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)需要循环左移一定次数(而后的具体操作步骤已在下述程序的注释中已详细给出)。
所以,解决问题的方法有很多,看你怎么去运用。
思路三:递归转换法
比如下面的例子
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,这就实现了整个反转。
如下图所示:
扩展:
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”。
本文是在原文的基础上,加入自己的总结,可能会简略。毕竟这样才是属于自己的。
题目描述:
定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部,如把字符串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”。
相关文章推荐
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术第一章、左旋转字符串
- 程序员编程艺术第一章、左旋转字符串
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术:第一章、左旋转字符串
- 算法题002 程序员编程艺术第一章 左旋转字符串
- 程序员编程艺术第一章、左旋转字符串
- 程序员编程艺术:第一章、左旋转字符串
- 程序员编程艺术第一章、左旋转字符串
- 程序员编程艺术_第一章左旋转字符串_C实现
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术:第一章、左旋转字符串
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术(算法卷):第一章、左旋转字符串
- 程序员编程艺术:第一章、左旋转字符串
- 程序员编程艺术(算法卷):第一章、左旋转字符串探讨
- 读程序员编程艺术第一章---左旋字符串(三)
- 读程序员编程艺术第一章---左旋字符串(二)
- 程序员编程艺术----1、左旋转字符串