左移字符串

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

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

(1)

递归方法

本文最初发布时,网友留言bluesmic说:楼主,谢谢你提出的研讨主题,很有学术和实践价值。关于思路二,本人提一个建议:思路二的代码,如果用递归的思想去简化,无论代码还是逻辑都会更加简单明了。
    就是说,把一个规模为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的右旋转。
updated:
Bluesmic的思路没有问题,他的思路以前很少有人提出。思路是通过递归将问题规模变小。当字符串总长度为n,左侧要旋转的部分长度为m,那么当从左向右循环交换长度为m的小段直到剩余部分为m’(n % m),此时m’ < m,已不能直接交换了。
此后,我们换一个思路,把该问题递归转化成规模大小为m’ +m,方向相反的同一问题。随着递归的进行,直到满足结束条件n % m==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,满足结束条件,返回结果。
     即从左至右,后从右至左,再从左至右,如此反反复复,直到满足条件,返回退出。
    代码如下,已测试正确(有待优化):

void reverseString(char* c,int len, int digit,bool isLeft){
	if(digit <=0 ) return;
	digit = digit % len;
	if(digit == 0) return; 
	int digitLeft = len % digit;
	
	
	if(isLeft){
		int index = 0;
		while(index + (2 * digit - 1) < len){
			int digitReversed = 0;
			while(digitReversed < digit){
				c[index] += c [index + digit];
				c[index + digit] = c[index] - c[index + digit];
				c[index] -= c[index + digit];
				digitReversed++;
				index++;
			}
		}
		reverseString(&c[index],digit+digitLeft,digitLeft,false);
	}else{
		int index = len-1;
		while(index - (2 * digit - 1) >= 0 ){
			int digitReversed = 0;
			while(digitReversed < digit){
				c[index] += c [index - digit];
				c[index - digit] = c[index] - c[index - digit];
				c[index] -= c[index - digit];
				digitReversed++;
				index--;
			}
		}
		index++;
		reverseString(&c[index-digit-digitLeft],digit+digitLeft,digitLeft,true);
	}
}

(2)三次翻转

对于这个问题,咱们换一个角度,可以这么做:
将一个字符串分成两部分,X和Y两个部分,在字符串上定义反转的操作X^T,即把X的所有字符反转(如,X="abc",那么X^T="cba"),那么我们可以得到下面的结论:(X^TY^T)^T=YX。显然我们这就可以转化为字符串的反转的问题了。

不是么?ok,就拿abcdef 这个例子来说,若要让def翻转到abc的前头,那么只要按下述3个步骤操作即可:
1、首先分为俩部分,X:abc,Y:def;
2、X->X^T,abc->cba, Y->Y^T,def->fed。
3、(X^TY^T)^T=YX,cbafed->defabc,即整个翻转。

    我想,这下,你应该一目了然了。
    其次,在《编程珠玑》上也有这样一个类似的问题,它的解法同本思路一致,如下图所示:


void reverseArray(char* c, int len){
	int index = 0;
	while(index < len/2){
		c[index] += c[len - 1 - index];
		c[len - 1 - index] = c[index] - c[len-1-index];
		c[index] -= c[len - 1 - index];
		index++;
	}
}

void leftString(char* c, int len, int digit){
	digit = digit % len;
	reverseArray(c,digit);
	reverseArray(&c[digit],len-digit);
	reverseArray(c,len);
}

(3)stl::rotate 算法的步步深入数组循环移位

对于左旋转字符串,我们知道每个单元都需要且只需要赋值一次,什么样的序列能保证每个单元都只赋值一次呢?
      1、对于正整数m、n互为质数的情况,通过以下过程得到序列的满足上面的要求:
 for i = 0: n-1
      k = i * m % n;
 end
    举个例子来说明一下,例如对于m=3,n=4的情况,
        1、我们得到的序列:即通过上述式子求出来的k序列,是0, 3, 2, 1。
        2、然后,你只要只需按这个顺序赋值一遍就达到左旋3的目的了:
    ch[0]->temp, ch[3]->ch[0], ch[2]->ch[3], ch[1]->ch[2], temp->ch[1];   (*) 
    ok,这是不是就是按上面(*)式子的顺序所依次赋值的序列阿?哈哈,很巧妙吧。当然,以上只是特例,作为一个循环链,相当于rotate算法的一次内循环。
     2、对于正整数m、n不是互为质数的情况(因为不可能所有的m,n都是互质整数对),那么我们把它分成一个个互不影响的循环链,正如flyinghearts所言,所有序号为 (j + i * m) % n(j为0到gcd(n, m)-1之间的某一整数,i = 0:n-1)会构成一个循环链,一共有gcd(n, m)个循环链,对每个循环链分别进行一次内循环就行了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值