1. 程式人生 > >經典面試題 之 數組的循環右移

經典面試題 之 數組的循環右移

-a span 長度 main ever wrap 寫入 -s 應該

經典面試題 之 數組的循環右移

題目的大意是將一個長度為n的數組A內的元素循環右移m位(當然左移也可以),比如數組 {1, 2, 3, 4, 5}右移3位之後就變成{3, 4, 5, 1, 2}。

這題最平凡的做法是開另一個大小一樣的數組B,遍歷一下,令B[(i + m) % n] = A[i],再將B的內容寫回到A即可。這個方法的時間復雜度為O(N),空間復雜度也為O(N)。

很明顯,需要優化空間的使用。有一種很優美但不太好懂的方法,是先將A的元素倒置,即{1, 2, 3, 4, 5}變成{5, 4, 3, 2, 1},然後將前m位倒置,即{3, 4, 5, 2, 1},再將後n-m位倒置,即{3, 4, 5, 1, 2}。完成。證明略,代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Reverse(int n, int *a) { for (int i = 0; i < n / 2; ++i) { int t = a[i]; a[i] = a[n - i - 1]; a[n - i - 1] = t; } } void ShiftRight1(int m, int n, int *a) { m %= n; if (m == 0) return; Reverse(n, a); Reverse(m, a); Reverse(n - m, a + m);
}

不過這種方法需要對每個位置寫入2次,看上去也不怎麽好,那有沒有更好的呢?

我們要做的只是把每個元素放到它應該在的位置,比如開頭的例子,1應該放在4的位置,把1放好之後,4就沒地方了,那4應該在哪呢,在2的位置,以此類推,就可以把所有的元素都放好,而且只放了一次。看上去這樣做很完美,但仔細想想就能想出反例子,比如{1, 2, 3, 4, 5, 6, 7, 8, 9}右移3位,就是1放在4個位置,4放在7的位置,然後7放回1,這時候一圈兜完了,但只排好了3個元素,剩下的6個元素沒有動過,怎麽辦呢?繼續下個唄,就是2,然後2、5、8也排好了,繼續3、6、9,這時候下一個元素是1了(因為1之前就被放在了4的位置),應該停止了,那程序怎麽會知道停在這裏了,於是就想到了最大公約數,9和3的最大公約數是3,於是做前3個數的循環就可以了,為什麽上一個例子只需做一次,因為元素個數(5)和移動位數(3)互質。

具體的數學證明略,直接上代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int gcd(int a, int b) { if (a == 0 || b == 0) { return a + b; } int t; while(b < 0) { t = a % b; a = b; b = t; } return a; } void ShiftRight2(int m, int n, int *a) { m %= n; if (m == 0) return; for (int i = 0, _i = gcd(n, m); i < _i; ++i) { int k = i; int t = a[k]; do { k = (k + m) % n; int tt = a[k]; a[k] = t; t = tt; } while(k != i); } }

以下是測試數據:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 void RunTest(int n, int m) { int *a = new int[n]; int *b = new int[n]; int *c = new int[n]; for (int i = 0; i < n; ++i) { a[i] = i; b[i] = i; c[i] = i; } ShiftRight1(m, n, b); ShiftRight2(m, n, c); bool f = true; for (int i = 0; i < n; ++i) { if (b[i] != c[i]) { f = false; break; } } if (!f) { cout << "A: "; for (int i = 0; i < n; ++i) { cout << a[i] << " "; } cout << endl; cout << "B: "; for (int i = 0; i < n; ++i) { cout << b[i] << " "; } cout << endl; cout << "C: "; for (int i = 0; i < n; ++i) { cout << c[i] << " "; } cout << endl; cout << endl; } delete[]a; delete[]b; delete[]c; } int main(){ for (int i = 1; i < 1000; ++i) { for (int j = 1; j <= i; ++j) { RunTest(i, j); } } return 0; }

經典面試題 之 數組的循環右移