1008 数组元素循环右移问题 ——C及C++实现

版权声明:本文为博主原创文章,转载请务必注明出处和作者,谢谢合作! https://blog.csdn.net/zhanshen112/article/details/84313616

题目

1008 数组元素循环右移问题 (20 point(s))

一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A​0​​A​1​​⋯A​N−1​​)变换为(A​N−M​​⋯A​N−1​​A​0​​A​1​​⋯A​N−M−1​​)(最后M个数循环移至最前面的M个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?

输入格式:

每个输入包含一个测试用例,第1行输入N(1≤N≤100)和M(≥0);第2行输入N个整数,之间用空格分隔。

输出格式:

在一行中输出循环右移M位以后的整数序列,之间用空格分隔,序列结尾不能有多余空格。

输入样例:

6 2
1 2 3 4 5 6

输出样例:

5 6 1 2 3 4

算法

此类题解题的通法

这道题属于模拟运动与计算的题目,是一类常见的题。比如模拟圆饼移动、乘除法、链表等等。解决这类问题的关键在于熟练掌握常见运动与计算的过程,并具有较熟练的编程能力。

算法

标准:数组元素循环右移只需要每次移动一位数字,然后移动几位就有几个循环即可。每一个移动过程中,将最后一位数字取出来放在tmp中,然后左边的数字都往右移动一位,最后再把tmp的数字放回到左边下标为0的位置。见代码1、2~

其他思路:这道题也可以采用这样一种思路

实际上就是把需要移动的全部存到一个新的数组中。然后再重新填回去a[]就行。见代码3。这里要注意M=0时,无法开辟长度为0 的数组,因此要单独将M>0拿出来。

填回去之后提交并不能完全AC,这里要注意题目只说了M>=0,没说M会小于N,因此不能AC的两个测试样例可能是M>N了,因此要将代码3改一改。也比较容易,因为如果数组完全移动N个位置的话会变回原数组,也就是说周期为N,因此只要将M%N,取余数就可以是最后的移动效果。见代码4。

采用这种方式,可以在M很大的时候,减小移动的时间复杂度,相比于代码2这种移动效率也会比较高。

那么问题来了,能不能将标准算法的代码2的移动效率在M>N时提高呢?显然也是可以的,只需要将第二个for循环里的i<M改为i<M%N即可。

修改之后的代码见代码5。代码最后的注释部分可以看出优化后的效果,时间上缩小了70倍左右。

对于代码4,实际上可以偷懒,将前后两部分取出来之后,可以不用再放回a[]数组,直接输出就行。由于AC时只看输出结果,因此并不影响,而且时间复杂度可以减少一半。见代码6.

网上见到了一个使用库函数的好方法。既然有对应的函数,就省了很多事啦~。见代码7.

reverse()函数包含在头文件<algorithm>中。该头文件还有常用的sort()函数。

C++ < algorithm > 中定义的reverse函数用于反转在[first,last)范围内的顺序

template <class BidirectionalIterator>
void reverse (BidirectionalIterator first,BidirectionalIterator last);

例如,交换vector容器中元素的顺序

vector<int> v={1,2,3,4,5};
reverse(v.begin(),v.end());//v的值为5,4,3,2,1

当然,你也可以通过它方便的反转string类的字符串

string str="C++REVERSE";
reverse(str.begin(),str.end());//str结果为ESREVER++C

该函数等价于通过调用iter_swap来交换元素位置

template <class BidirectionalIterator>
void reverse (BidirectionalIterator first, BidirectionalIterator last)
{
    while ((first!=last)&&(first!=--last))
    {
        std::iter_swap (first,last);
        ++first;
    }
}

标准库的begin()和end()函数是C++11新标准引入的函数,可以对数组类型进行操作,返回其首尾指针,对标准库容器操作,返回相应迭代器。

标准库容器的begin()和end()成员函数属于对应类的成员,返回的是对象容器的首尾迭代器。


新标准库的begin()和end()函数可以让我们更容易的获取数组的首尾指针(注意尾指针是最后一个元素的下一个地址)

试了一下,begin和end与vector容器搭配起来,再与reverse在一起使用,超好用!!!

代码

代码1,这是以前的代码

//PAT1008V1
#include <stdio.h>

int main(){
	int a[100],temp,i,j,n,m;
	scanf("%d %d",&n,&m);
	
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);	//init
	
//	for(i=0;i<n;i++)
//		printf("%d",a[i]);	//print
		
	for(i=0;i<m;i++){
		temp=a[n-1];
		for(j=n-1;j>0;j--){
			 
			a[j]=a[j-1];
		}
		a[0]=temp;
	}
	
	for(i=0;i<n;i++)
		printf("%d%c",a[i],i==(n-1)?'\0':' ');	//print
	
	return 0;
}

可以看出来具有初步的算法思想,但是代码简洁性和凝练度需要提高。另外从调试的注释能够看出来,代码能力还不够。

代码2,现在的代码

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];
	for(int i=0;i<M;i++){
		tmp=a[N-1];
		for(int j=N-1;j>0;j--)	a[j]=a[j-1];	//从倒数第二位开始依次后移一位 
		a[0]=tmp;	//挪出来的最后一位放到第一位上去 
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

代码3,重新开辟数组,将对应移动部分存起来,但不能完全AC

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M]={0},c[N-M]={0};
		for(int i=0;i<M;i++)	b[i]=a[N-M+i];	//将a[]后面需要移到前面的部分取出来 
		for(int i=0;i<N-M;i++)	c[i]=a[i]; 	//将a[]前面需要移到后面的部分取出来 
		for(int i=0;i<M;i++)	a[i]=b[i];	//将b[]放到a[]的前面来  
		for(int i=M;i<N;i++) 	a[i]=c[i-M];	//将c[]放到a[]的后面来  
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

代码4,注意M>N的情况,提高M>N时的移动效率

#include <iostream>
using namespace std;

int main(){
	int N,M;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M%N]={0},c[N-M%N]={0};
		for(int i=0;i<M%N;i++)	b[i]=a[N-M%N+i];	//将a[]后面需要移到前面的部分取出来 
		for(int i=0;i<N-M%N;i++)	c[i]=a[i]; 	//将a[]前面需要移到后面的部分取出来 
		for(int i=0;i<M%N;i++)	a[i]=b[i];	//将b[]放到a[]的前面来  
		for(int i=M%N;i<N;i++) 	a[i]=c[i-M%N];	//将c[]放到a[]的后面来  
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
} 

代码5,通法的优化

#include <iostream>
using namespace std;

int main(){
	int N,M,tmp;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];
	for(int i=0;i<M%N;i++){
		tmp=a[N-1];
		for(int j=N-1;j>0;j--)	a[j]=a[j-1];	//从倒数第二位开始依次后移一位 
		a[0]=tmp;	//挪出来的最后一位放到第一位上去 
	}
	for(int i=0;i<N;i++)	printf("%d%c",a[i],i==N-1?'\0':' ');
	return 0;
}

/*
output:
当采用M时
6 20000
1 2 3 4 5 6
5 6 1 2 3 4

耗时 14.32s 

当采用M%N时
6 20000
1 2 3 4 5 6
5 6 1 2 3 4

耗时 0.4798s 
*/ 

代码6,偷懒做法,并没有移动……

#include <iostream>
using namespace std;

int main(){
	int N,M;	cin>>N>>M;
	int a[N]={0};
	for(int i=0;i<N;i++)	cin>>a[i];	
	if(M>0){
		int b[M%N]={0},c[N-M%N]={0};
		for(int i=0;i<M%N;i++)	cout<<a[N-M%N+i]<<" ";	//将a[]后面需要移到前面的部分直接输出 
		for(int i=0;i<N-M%N-1;i++)	cout<<a[i]<<" "; 	//将a[]前面需要移到后面的部分直接输出
	}
	cout<<a[N-M%N-1];	//最后一个元素由于其后没有空格,单独输出,省事 
	return 0;
} 

代码7。使用库函数

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (int i = 0; i < n; i++)
        cin >> a[i];
    m %= n;
    if (m != 0) {
        reverse(begin(a), begin(a) + n);
        reverse(begin(a), begin(a) + m);
        reverse(begin(a) + m, begin(a) + n);
    }
    for (int i = 0; i < n - 1; i++)
        cout << a[i] << " ";
    cout << a[n - 1];
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhanshen112/article/details/84313616