题目
1008 数组元素循环右移问题 (20 point(s))
一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A0A1⋯AN−1)变换为(AN−M⋯AN−1A0A1⋯AN−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;
}