题目的大意是将一个长度为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}。完成。证明略,代码如下:
01
02
03
04
05
06
07
08
09
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)互质。
具体的数学证明略,直接上代码:
01
02
03
04
05
06
07
08
09
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); } } |
以下是测试数据:
01
02
03
04
05
06
07
08
09
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; } |