陣列的位移運算

weixin_34391854發表於2014-07-29

題目:

設計一個演算法,把一個含有N個元素的陣列迴圈右移K位,要求時間複雜度為O(N),且只允許使用兩個附加變數。

分析:

通常人們的直覺可能是每次將陣列中的元素右移一位,但這樣的複雜度為O(K*N),不符合題目的要求。


假如陣列為abcd1234,迴圈右移4位的話,我們希望到達的狀態是1234abcd。不妨設K是一個非負的整數,當K為負整數的時候,右移K位,相當於左移(-K)位。左移和右移在本質上是一樣的。

【解法一】

大家開始可能會有這樣的潛在假設,K<N。事實上,很多時候也的確是這樣的。但嚴格地說,我們不能用這樣的“慣性思維”來思考問題。尤其在程式設計的時候,全面地考慮問題是很重要的,K可能是一個遠大於N的整數,在這個時候,上面的解法是需要改進的。

仔細觀察迴圈右移的特點,不難發現:每個元素右移N位後都會回到自己的位置上。因此,如果K > N,右移K-N之後的陣列序列跟右移K位的結果是一樣的。進而可得出一條通用的規律:右移K位之後的情形,跟右移K’= K % N位之後的情形一樣。
 1 RightShift(int* arr, int N, int K)
 2 {
 3     K %= N;
 4     while(K--) {
 5         int t = arr[N - 1];
 6         for(int i = N - 1; i > 0; i --)
 7             arr[i] = arr[i - 1];
 8         arr[0] = t;
 9     }
10 }

可見,增加考慮迴圈右移的特點之後,演算法複雜度降為O(N2),這跟K無關,與題目的要求又接近了一步。但時間複雜度還不夠低,接下來讓我們繼續挖掘迴圈右移前後,陣列之間的關聯。

【解法二】

假設原陣列序列為abcd1234,要求變換成的陣列序列為1234abcd,即迴圈右移了4位。比較之後,不難看出,其中有兩段的順序是不變的:1234和abcd,可把這兩段看成兩個整體。右移K位的過程就是把陣列的兩部分交換一下。變換的過程通過以下步驟完成:

1.   逆序排列abcd:abcd1234 → dcba1234;

2.   逆序排列1234:dcba1234 → dcba4321;

3.   全部逆序:dcba4321 → 1234abcd。

虛擬碼可以參考如下:
 1 Reverse(int* arr, int b, int e)
 2 {
 3     for(; b < e; b++, e--) {
 4         int temp = arr[e];
 5         arr[e] = arr[b];
 6         arr[b] = temp;
 7     }
 8 } 
 9 
10 RightShift(int* arr, int N, int k)
11 {
12     K %= N;
13     Reverse(arr, 0, N – K - 1);
14     Reverse(arr, N - K, N - 1);
15     Reverse(arr, 0, N - 1);
16 }
這樣,我們就可以線上性時間內實現右移操作了。
 
來源於微信:一天一道演算法題

相關文章