左旋转字符串1(转自研究者july)

第一节、左旋转字符串
题目描述:
定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。
如把字符串 abcdef 左旋转 2 位得到字符串 cdefab。
请实现字符串左旋转的函数,要求对长度为 n 的字符串操作的时间复杂度为
O(n),空间复杂度为O(1)
编程之美上有这样一个类似的问题,咱们先来看一下:
设计一个算法,把一个含有 N 个元素的数组循环右移 K 位,要求时间复杂度为 O(N),
且只允许使用两个附加变量。
分析:
6
我们先试验简单的办法,可以每次将数组中的元素右移一位,循环 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--)
{
7
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[b];
arr[b] = temp;
}
}
RightShift(int* arr, int N, int k)
{
K %= N;
Reverse(arr, 0, N K - 1);
Reverse(arr, N - K, N - 1);
8
Reverse(arr, 0, N - 1);
}
这样,我们就可以在线性时间内实现右移操作了。
稍微总结下:
编程之美上,
(限制书中思路的根本原因是,题目要求: 且只允许使用两个附加变量,去掉这个限制,
思路便可如泉喷涌)
1 、第一个想法 ,是一个字符一个字符的右移,所以,复杂度为 O( N*K)
2、后来,它改进了,通过这条规律:右移 K 位之后的情形,跟右移 K’= K % N 位之后的情
形一样
复杂度为 O( N^2)
3、直到最后,它才提出三次翻转的算法,得到线性复杂度。
下面,你将看到,本章里我们的做法是:
1 、三次翻转,直接线性
2、两个指针逐步翻转,线性
3、 stl 的 rotate 算法,线性


// Maximum Product Subarray.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include<stdlib.h>
#include <stdio.h>
using namespace std;

//右移
//这个算法可以实现数组的循环右移,但是算法复杂度为 O( K * N).
void right_mov1(int *ary,int N,int K)
{
	int temp;
	while(K--)
	{
		temp=ary[0];
		for(int i=0;i<N-1;i++)
		{
			ary[i]=ary[i+1];
		}
		ary[N-1]=temp;
	}
}
//右移 K 位之后的情形,跟右移 K’= K % N 位之后的情形一样.
void right_mov2(int *ary,int N,int K)
{
	K=K%N;
	int temp;
	while(K--)
	{
		temp=ary[0];
		for(int i=0;i<N-1;i++)
		{
			ary[i]=ary[i+1];
		}
		ary[N-1]=temp;
	}
}

//将一个序列反序.
void reversed(int *ary,int N)
{
	for(int i=0;i<(N/2+N%2);i++)
	{
		int j=N-1-i;
		int temp;
		temp=ary[i];ary[i]=ary[j];ary[j]=temp;	
	}
}

void reversed1(int *ary,int a,int b)
{
	for(int i=a;i<=((b-a)/2+a);i++)
	{
		int j=b-i+a;
		int temp;
		temp=ary[i];ary[i]=ary[j];ary[j]=temp;
	}
}
void right_mov3(int *ary,int N,int K)
{
	K=K%N;
	reversed1(ary,0,K-1);
	reversed1(ary,K,N-1);
	reversed1(ary,0,N-1);	
}

int _tmain(int argc, _TCHAR* argv[])
{
	int ary[10]={1,2,3,4,5,6,7,8,9,10};
	for(int i=0;i<10;i++)
		cout<<ary[i]<<' ';
	cout<<endl;

	//right_mov1(ary,10,2);
	 //right_mov2(ary,10,12);
	//reversed1(ary,3,9);
	right_mov3(ary,10,2);

	for(int i=0;i<10;i++)
		cout<<ary[i]<<' ';
	cout<<endl;


	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值