左旋转字符串LeftRotateString -- 翻转单词顺序
2013-05-17 09:03
387 查看
源自:http://blog.csdn.net/v_JULY_v/article/details/6322882
题目描述:
定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
如把字符串abcdef左旋转2位得到字符串cdefab。
请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1)。
分析:
我们先试验简单的办法,可以每次将数组中的元素右移一位,循环K次。
abcd1234→4abcd123→34abcd12→234abcd1→1234abcd。
RightShift(int* arr, int N, int K)
{
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
虽然这个算法可以实现数组的循环右移,但是算法复杂度为O(K * N),不符合题目的要求,要继续探索。
假如数组为abcd1234,循环右移4位的话,我们希望到达的状态是1234abcd。
不妨设K是一个非负的整数,当K为负整数的时候,右移K位,相当于左移(-K)位。
左移和
解法一:
大家开始可能会有这样的潜在假设,K<N。事实上,很多时候也的确是这样的。但严格来说,我们不能用这样的“惯性思维”来思考问题。
尤其在编程的时候,全面地考虑问题是很重要的,K可能是一个远大于N的整数,在这个时候,上面的解法是需要改进的。
仔细观察循环右移的特点,不难发现:每个元素右移N位后都会回到右移在本质上是一样的。自己的位置上。因此,如果K > N,右移K-N之后的数组序列跟右移K位的结果是一样的。
进而可得出一条通用的规律:
右移K位之后的情形,跟右移K’= K % N位之后的情形一样,如代码清单2-34所示。
//代码清单2-34
RightShift(int* arr, int N, int K)
{
K %= N;
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
可见,增加考虑循环右移的特点之后,算法复杂度降为O(N^2),这跟K无关,与题目的要求又接近了一步。但时间复杂度还不够低,接下来让我们继续挖掘循环右移前后,数组之间的关联。
解法二:
假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。
变换的过程通过以下步骤完成:
逆序排列abcd:abcd1234 → dcba1234;
逆序排列1234:dcba1234 → dcba4321;
全部逆序:dcba4321 → 1234abcd。
伪代码可以参考清单2-35。
//代码清单2-35
Reverse(int* arr, int b, int e)
{
for(; b < e; b++, e--)
{
int temp = arr[e];
arr[e] = arr;
arr[b] = temp;
}
}
RightShift(int* arr, int N, int k)
{
K %= N;
Reverse(arr, 0, N – K - 1);
Reverse(arr, N - K, N - 1);
Reverse(arr, 0, N - 1);
}
这样,我们就可以在线性时间内实现右移操作了。
本章里我们的做法是:
1、三次翻转,直接线性
2、两个指针逐步翻转,线性
3、stl的rotate算法,线性
就拿abcdef 这个例子来说
1、首先分为俩部分,X:abc,Y:def;
2、X->X^T,abc->cba, Y->Y^T,def->fed。
3、(X^TY^T)^T=YX,cbafed->defabc,即整个翻转。
#include <stdio.h>
#include <string.h>
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 *left(char *s, int pos) //pos为要旋转的字符个数,或长度,下面主函数测试中,pos=3。
{
int len = strlen(s);
invert(s, s + (pos - 1)); //如上,X->X^T,即 abc->cba
invert(s + pos, s + (len - 1)); //如上,Y->Y^T,即 def->fed
invert(s, s + (len - 1)); //如上,整个翻转,(X^TY^T)^T=YX,即 cbafed->defabc。
return s;
}
int main()
{
char s[] = "abcdefghij";
puts(left(s, 3));
return 0;
}
给出一段[b]c实现的代码。
然后,我们可以看到c的高效与简洁。
#include <cstdio>
#include <cstring>
void rotate(char *start, char *end)
{
while(start != NULL && end !=NULL && start<end)
{
char temp=*start;
*start=*end;
*end=temp;
start++;
end--;
}
}
void leftrotate(char *p,int m)
{
if(p==NULL)
return ;
int len=strlen(p);
if(m>0&&m<=len)
{
char *xfirst,*xend;
char *yfirst,*yend;
xfirst=p;
xend=p+m-1;
yfirst=p+m;
yend=p+len-1;
rotate(xfirst,xend);
rotate(yfirst,yend);
rotate(p,p+len-1);
}
}
int main(void)
{
char str[]="abcdefghij";
leftrotate(str,3);
printf("%s/n",str);
return 0;
}
方法二:两指针逐步翻转
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)
针对上述过程给出的图解:
如果是要左旋十个元素的序列:abcdefghij,ok,下面,就举这个例子,对abcdefghij序列进行左旋转操作:
如果abcdef ghij要变成defghij abc:
abcdef ghij
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
下面,再针对上述过程,画个图清晰说明下,如下所示:
ok,咱们来好好彻底总结一下此思路二:(就4点,请仔细阅读):
1、首先让p1=ch[0],p2=ch[m],即让p1,p2相隔m的距离;
2、判断p2+m-1是否越界,如果没有越界转到3,否则转到4(abcdefgh这8个字母的字符串,以4左旋,那么初始时p2指向e,p2+4越界了,但事实上p2至p2+m-1是m个字符,可以再做一个交换)。
3、不断交换*p1与*p2,然后p1++,p2++,循环m次,然后转到2。
4、此时p2+m-1 已经越界,在此只需处理尾巴。过程如下:
4.1 通过n-p2得到p2与尾部之间元素个数r,即我们要前移的元素个数。
4.2 以下过程执行r次:
ch[p2]<->ch[p2-1],ch[p2-1]<->ch[p2-2],....,ch[p1+1]<->ch[p1];p1++;p2++;
代码编写如下:
#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;
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。
}
int main()
{
string ch="abcdefghijk";
rotate(ch,3);
cout<<ch<<endl;
return 0;
}
注意:上文中都是假设m<n,且如果令m=m%n,这样m允许大于n。另外,各位要记得处理指针为空的情况。
*/
#include<iostream>
#include<string>
#define positiveMod(m,n) ((m) % (n) + (n)) % (n)
/*
*左旋字符串str,m为负数时表示右旋abs(m)个字母
*/
void rotate(std::string &str, int m) {
if (str.length() == 0)
return;
int n = str.length();
//处理大于str长度及m为负数的情况,positiveMod可以取得m为负数时对n取余得到正数
m = positiveMod(m,n);
if (m == 0)
return;
// if (m % n <= 0)
// return;
int p1 = 0, p2 = m;
int round;
//p2当前所指和之后的m-1个字母共m个字母,就可以和p2前面的m个字母交换。
while (p2 + m - 1 < n) {
round = m;
while (round--) {
std::swap(str[p1], str[p2]);
p1++;
p2++;
}
}
//剩下的不足m个字母逐个交换
int r = n - p2;
while (r--) {
int i = p2;
while (i > p1) {
std::swap(str[i], str[i - 1]);
i--;
}
p2++;
p1++;
}
}
//测试
int main(int argc, char **argv) {
// std::cout << ((-15) % 7 + 7) % 7 << std::endl;
// std::cout << (-15) % 7 << std::endl;
std::string ch = "abcdefg";
int len = ch.length();
for (int m = -2 * len; m <= len * 2; m++) {
//由于传给rotate的是string的引用,所以这里每次调用都用了一个新的字符串
std::string s = "abcdefg";
rotate(s, m);
std::cout << positiveMod(m,len) << ": " << s << std::endl;
}
return 0;
}
方法三:通过递归转换,缩小问题之规模
把一个规模为N的问题化解为规模为M(M<N)的问题。
举例来说,设字符串总长度为L,左侧要旋转的部分长度为s1,那么当从左向右循环交换长度为s1的小段,直到最后,由于剩余的部分长度为s2(s2==L%s1)而不能直接交换。
该问题可以递归转化成规模为s1+s2的,方向相反(从右向左)的同一个问题。随着递归的进行,左右反复回荡,直到某一次满足条件L%s1==0而交换结束。
设原始问题为:将“123abcdefg”左旋转为“abcdefg123”,即总长度为10,旋转部("123")长度为3的左旋转。按照思路二的运算,演变过程为“123abcdefg”->"abc123defg"->"abcdef123g"。这时,"123"无法和"g"作对调,该问题递归转化为:将“123g”右旋转为"g123",即总长度为4,旋转部("g")长度为1的右旋转。
举个具体事例说明,如下:
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,满足结束条件,返回结果。
即从左至右,后从右至左,再从左至右,如此反反复复,直到满足条件,返回退出。
代码如下,已测试正确(有待优化):
#include <iostream>
#include <string>
using namespace std;
void rotate(string &str, int n, int m, int head, int tail, bool flag)
{
//n 待处理部分的字符串长度,m:待处理部分的旋转长度
//head:待处理部分的头指针,tail:待处理部分的尾指针
//flag = true进行左旋,flag = false进行右旋
// 返回条件
if (head == tail || m <= 0)
return;
if (flag == true)
{
int p1 = head;
int p2 = head + m; //初始化p1,p2
//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
//(相信,经过上文中那么多繁杂的叙述,此类的转换过程,你应该是了如指掌了。)
int k = (n - m) - n % m; //p1,p2移动距离,向右移六步
/*---------------------
解释下上面的k = (n - m) - n % m的由来:
yansha:
以p2为移动的参照系:
n-m 是开始时p2到末尾的长度,n%m是尾巴长度
(n-m)-n%m就是p2移动的距离
比如 abc def efg hi
开始时p2->d,那么n-m 为def efg hi的长度8,
n%m 为尾巴hi的长度2,
因为我知道abc要移动到hi的前面,所以移动长度是
(n-m)-n%m = 8-2 = 6。
*/
for (int i = 0; i < k; i++, p1++, p2++)
swap(str[p1], str[p2]);
rotate(str, n - k, n % m, p1, tail, false); //flag标志变为false,结束左旋,下面,进入右旋
}
else
{
//2、右旋:问题变成gk左移到abc前面,此时n = m’ + m = 5,m = 2,m’ = n % m 1;
//abc gk -> a gk bc
int p1 = tail;
int p2 = tail - m;
// p1,p2移动距离,向左移俩步
int k = (n - m) - n % m;
for (int i = 0; i < k; i++, p1--, p2--)
swap(str[p1], str[p2]);
rotate(str, n - k, n % m, head, p1, true); //再次进入上面的左旋部分,
//3、左旋:问题变成a右移到gk后面,此时n = m’ + m = 3,m = 1,m’ = n % m = 0;
//a gk bc-> gk a bc。 由于此刻,n % m = 0,满足结束条件,返回结果。
}
}
int main()
{
int i=3;
string str = "abcdefghijk";
int len = str.length();
rotate(str, len, i % len, 0, len - 1, true);
cout << str.c_str() << endl; //转化成字符数组的形式输出
return 0;
}
另一个类似的问题:翻转单词顺序
题目描述:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。
例如输入“I am a student”,则输出“student. a am I”。
思路:对于整个句子,先做一次翻转Reverse(),可得到:tneduts a ma i
所有字符颠倒过来,在对每一个单词做一次翻转ReverseSentence(),即可得到:student a am i
char* ReverseSentence(char *pData)
{
if(pData == NULL)
return NULL;
char *pBegin = pData;
char *pEnd = pData;
while(*pEnd != '\0')
pEnd ++;
pEnd--;
// 翻转整个句子
Reverse(pBegin, pEnd);
// 翻转句子中的每个单词
pBegin = pEnd = pData;
while(*pBegin != '\0')
{
if(*pBegin == ' ')
{
pBegin ++;
pEnd ++;
}
else if(*pEnd == ' ' || *pEnd == '\0')
{
Reverse(pBegin, --pEnd);
pBegin = ++pEnd;
}
else
{
pEnd ++;
}
}
return pData;
}
题目描述:
定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
如把字符串abcdef左旋转2位得到字符串cdefab。
请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1)。
分析:
我们先试验简单的办法,可以每次将数组中的元素右移一位,循环K次。
abcd1234→4abcd123→34abcd12→234abcd1→1234abcd。
RightShift(int* arr, int N, int K)
{
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
虽然这个算法可以实现数组的循环右移,但是算法复杂度为O(K * N),不符合题目的要求,要继续探索。
假如数组为abcd1234,循环右移4位的话,我们希望到达的状态是1234abcd。
不妨设K是一个非负的整数,当K为负整数的时候,右移K位,相当于左移(-K)位。
左移和
解法一:
大家开始可能会有这样的潜在假设,K<N。事实上,很多时候也的确是这样的。但严格来说,我们不能用这样的“惯性思维”来思考问题。
尤其在编程的时候,全面地考虑问题是很重要的,K可能是一个远大于N的整数,在这个时候,上面的解法是需要改进的。
仔细观察循环右移的特点,不难发现:每个元素右移N位后都会回到右移在本质上是一样的。自己的位置上。因此,如果K > N,右移K-N之后的数组序列跟右移K位的结果是一样的。
进而可得出一条通用的规律:
右移K位之后的情形,跟右移K’= K % N位之后的情形一样,如代码清单2-34所示。
//代码清单2-34
RightShift(int* arr, int N, int K)
{
K %= N;
while(K--)
{
int t = arr[N - 1];
for(int i = N - 1; i > 0; i --)
arr[i] = arr[i - 1];
arr[0] = t;
}
}
可见,增加考虑循环右移的特点之后,算法复杂度降为O(N^2),这跟K无关,与题目的要求又接近了一步。但时间复杂度还不够低,接下来让我们继续挖掘循环右移前后,数组之间的关联。
解法二:
假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。
变换的过程通过以下步骤完成:
逆序排列abcd:abcd1234 → dcba1234;
逆序排列1234:dcba1234 → dcba4321;
全部逆序:dcba4321 → 1234abcd。
伪代码可以参考清单2-35。
//代码清单2-35
Reverse(int* arr, int b, int e)
{
for(; b < e; b++, e--)
{
int temp = arr[e];
arr[e] = arr;
arr[b] = temp;
}
}
RightShift(int* arr, int N, int k)
{
K %= N;
Reverse(arr, 0, N – K - 1);
Reverse(arr, N - K, N - 1);
Reverse(arr, 0, N - 1);
}
这样,我们就可以在线性时间内实现右移操作了。
本章里我们的做法是:
1、三次翻转,直接线性
2、两个指针逐步翻转,线性
3、stl的rotate算法,线性
就拿abcdef 这个例子来说
1、首先分为俩部分,X:abc,Y:def;
2、X->X^T,abc->cba, Y->Y^T,def->fed。
3、(X^TY^T)^T=YX,cbafed->defabc,即整个翻转。
#include <stdio.h>
#include <string.h>
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 *left(char *s, int pos) //pos为要旋转的字符个数,或长度,下面主函数测试中,pos=3。
{
int len = strlen(s);
invert(s, s + (pos - 1)); //如上,X->X^T,即 abc->cba
invert(s + pos, s + (len - 1)); //如上,Y->Y^T,即 def->fed
invert(s, s + (len - 1)); //如上,整个翻转,(X^TY^T)^T=YX,即 cbafed->defabc。
return s;
}
int main()
{
char s[] = "abcdefghij";
puts(left(s, 3));
return 0;
}
给出一段[b]c实现的代码。
然后,我们可以看到c的高效与简洁。
#include <cstdio>
#include <cstring>
void rotate(char *start, char *end)
{
while(start != NULL && end !=NULL && start<end)
{
char temp=*start;
*start=*end;
*end=temp;
start++;
end--;
}
}
void leftrotate(char *p,int m)
{
if(p==NULL)
return ;
int len=strlen(p);
if(m>0&&m<=len)
{
char *xfirst,*xend;
char *yfirst,*yend;
xfirst=p;
xend=p+m-1;
yfirst=p+m;
yend=p+len-1;
rotate(xfirst,xend);
rotate(yfirst,yend);
rotate(p,p+len-1);
}
}
int main(void)
{
char str[]="abcdefghij";
leftrotate(str,3);
printf("%s/n",str);
return 0;
}
方法二:两指针逐步翻转
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)
针对上述过程给出的图解:
如果是要左旋十个元素的序列:abcdefghij,ok,下面,就举这个例子,对abcdefghij序列进行左旋转操作:
如果abcdef ghij要变成defghij abc:
abcdef ghij
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
下面,再针对上述过程,画个图清晰说明下,如下所示:
ok,咱们来好好彻底总结一下此思路二:(就4点,请仔细阅读):
1、首先让p1=ch[0],p2=ch[m],即让p1,p2相隔m的距离;
2、判断p2+m-1是否越界,如果没有越界转到3,否则转到4(abcdefgh这8个字母的字符串,以4左旋,那么初始时p2指向e,p2+4越界了,但事实上p2至p2+m-1是m个字符,可以再做一个交换)。
3、不断交换*p1与*p2,然后p1++,p2++,循环m次,然后转到2。
4、此时p2+m-1 已经越界,在此只需处理尾巴。过程如下:
4.1 通过n-p2得到p2与尾部之间元素个数r,即我们要前移的元素个数。
4.2 以下过程执行r次:
ch[p2]<->ch[p2-1],ch[p2-1]<->ch[p2-2],....,ch[p1+1]<->ch[p1];p1++;p2++;
代码编写如下:
#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;
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。
}
int main()
{
string ch="abcdefghijk";
rotate(ch,3);
cout<<ch<<endl;
return 0;
}
注意:上文中都是假设m<n,且如果令m=m%n,这样m允许大于n。另外,各位要记得处理指针为空的情况。
*/
#include<iostream>
#include<string>
#define positiveMod(m,n) ((m) % (n) + (n)) % (n)
/*
*左旋字符串str,m为负数时表示右旋abs(m)个字母
*/
void rotate(std::string &str, int m) {
if (str.length() == 0)
return;
int n = str.length();
//处理大于str长度及m为负数的情况,positiveMod可以取得m为负数时对n取余得到正数
m = positiveMod(m,n);
if (m == 0)
return;
// if (m % n <= 0)
// return;
int p1 = 0, p2 = m;
int round;
//p2当前所指和之后的m-1个字母共m个字母,就可以和p2前面的m个字母交换。
while (p2 + m - 1 < n) {
round = m;
while (round--) {
std::swap(str[p1], str[p2]);
p1++;
p2++;
}
}
//剩下的不足m个字母逐个交换
int r = n - p2;
while (r--) {
int i = p2;
while (i > p1) {
std::swap(str[i], str[i - 1]);
i--;
}
p2++;
p1++;
}
}
//测试
int main(int argc, char **argv) {
// std::cout << ((-15) % 7 + 7) % 7 << std::endl;
// std::cout << (-15) % 7 << std::endl;
std::string ch = "abcdefg";
int len = ch.length();
for (int m = -2 * len; m <= len * 2; m++) {
//由于传给rotate的是string的引用,所以这里每次调用都用了一个新的字符串
std::string s = "abcdefg";
rotate(s, m);
std::cout << positiveMod(m,len) << ": " << s << std::endl;
}
return 0;
}
方法三:通过递归转换,缩小问题之规模
把一个规模为N的问题化解为规模为M(M<N)的问题。
举例来说,设字符串总长度为L,左侧要旋转的部分长度为s1,那么当从左向右循环交换长度为s1的小段,直到最后,由于剩余的部分长度为s2(s2==L%s1)而不能直接交换。
该问题可以递归转化成规模为s1+s2的,方向相反(从右向左)的同一个问题。随着递归的进行,左右反复回荡,直到某一次满足条件L%s1==0而交换结束。
设原始问题为:将“123abcdefg”左旋转为“abcdefg123”,即总长度为10,旋转部("123")长度为3的左旋转。按照思路二的运算,演变过程为“123abcdefg”->"abc123defg"->"abcdef123g"。这时,"123"无法和"g"作对调,该问题递归转化为:将“123g”右旋转为"g123",即总长度为4,旋转部("g")长度为1的右旋转。
举个具体事例说明,如下:
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,满足结束条件,返回结果。
即从左至右,后从右至左,再从左至右,如此反反复复,直到满足条件,返回退出。
代码如下,已测试正确(有待优化):
#include <iostream>
#include <string>
using namespace std;
void rotate(string &str, int n, int m, int head, int tail, bool flag)
{
//n 待处理部分的字符串长度,m:待处理部分的旋转长度
//head:待处理部分的头指针,tail:待处理部分的尾指针
//flag = true进行左旋,flag = false进行右旋
// 返回条件
if (head == tail || m <= 0)
return;
if (flag == true)
{
int p1 = head;
int p2 = head + m; //初始化p1,p2
//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
//(相信,经过上文中那么多繁杂的叙述,此类的转换过程,你应该是了如指掌了。)
int k = (n - m) - n % m; //p1,p2移动距离,向右移六步
/*---------------------
解释下上面的k = (n - m) - n % m的由来:
yansha:
以p2为移动的参照系:
n-m 是开始时p2到末尾的长度,n%m是尾巴长度
(n-m)-n%m就是p2移动的距离
比如 abc def efg hi
开始时p2->d,那么n-m 为def efg hi的长度8,
n%m 为尾巴hi的长度2,
因为我知道abc要移动到hi的前面,所以移动长度是
(n-m)-n%m = 8-2 = 6。
*/
for (int i = 0; i < k; i++, p1++, p2++)
swap(str[p1], str[p2]);
rotate(str, n - k, n % m, p1, tail, false); //flag标志变为false,结束左旋,下面,进入右旋
}
else
{
//2、右旋:问题变成gk左移到abc前面,此时n = m’ + m = 5,m = 2,m’ = n % m 1;
//abc gk -> a gk bc
int p1 = tail;
int p2 = tail - m;
// p1,p2移动距离,向左移俩步
int k = (n - m) - n % m;
for (int i = 0; i < k; i++, p1--, p2--)
swap(str[p1], str[p2]);
rotate(str, n - k, n % m, head, p1, true); //再次进入上面的左旋部分,
//3、左旋:问题变成a右移到gk后面,此时n = m’ + m = 3,m = 1,m’ = n % m = 0;
//a gk bc-> gk a bc。 由于此刻,n % m = 0,满足结束条件,返回结果。
}
}
int main()
{
int i=3;
string str = "abcdefghijk";
int len = str.length();
rotate(str, len, i % len, 0, len - 1, true);
cout << str.c_str() << endl; //转化成字符数组的形式输出
return 0;
}
另一个类似的问题:翻转单词顺序
题目描述:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。
句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。
例如输入“I am a student”,则输出“student. a am I”。
思路:对于整个句子,先做一次翻转Reverse(),可得到:tneduts a ma i
所有字符颠倒过来,在对每一个单词做一次翻转ReverseSentence(),即可得到:student a am i
char* ReverseSentence(char *pData)
{
if(pData == NULL)
return NULL;
char *pBegin = pData;
char *pEnd = pData;
while(*pEnd != '\0')
pEnd ++;
pEnd--;
// 翻转整个句子
Reverse(pBegin, pEnd);
// 翻转句子中的每个单词
pBegin = pEnd = pData;
while(*pBegin != '\0')
{
if(*pBegin == ' ')
{
pBegin ++;
pEnd ++;
}
else if(*pEnd == ' ' || *pEnd == '\0')
{
Reverse(pBegin, --pEnd);
pBegin = ++pEnd;
}
else
{
pEnd ++;
}
}
return pData;
}
void Reverse(char *pBegin, char *pEnd) {//翻转 if (pBegin != NULL && pEnd != NULL) return ; while (pBegin < pEnd) { char temp = *pBegin; *pBegin = *pEnd; *pEnd = temp; pBegin++; pEnd--; } }
相关文章推荐
- 面试题42:翻转单词顺序VS左旋转字符串
- 【剑指offer】6.3知识迁移能力——面试题42:翻转单词顺序VS左旋转字符串
- 剑指offer 面试题42 翻转单词顺序 | 左旋转字符串
- 面试题42:翻转单词顺序VS左旋转字符串
- 剑指offer-第六章面试中的各项能力(翻转单词的顺序VS左旋转字符串)
- 面试算法(四十二)翻转单词顺序VS左旋转字符串
- 《剑指Offer》学习笔记--面试题42:翻转单词顺序VS坐旋转字符串
- 剑指offer 面试题42—翻转单词顺序VS左旋转字符串
- 剑指offer-面试题42-翻转单词顺序VS左旋转字符串
- 剑指offer——翻转单词顺序VS左旋转字符串
- 剑指offer-面试题42:翻转单词顺序VS左旋转字符串
- 每日一题--翻转单词顺序||左旋转字符串
- 翻转单词顺序VS左旋转字符串
- 翻转单词顺序 VS 左旋转字符串
- 剑指offer-面试题42-翻转单词顺序VS左旋转字符串
- 21.左旋转字符串[LeftRotateString]
- 剑指Offer面试题:34.翻转单词顺序VS左旋转字符串
- 翻转单词顺序与左旋转字符串
- 剑指offer——翻转单词顺序VS左旋转字符串
- 翻转单词顺序 VS 左旋转字符串