1008 陣列元素迴圈右移問題 ——C及C++實現
題目
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;
}