NC110 旋转数组
题目大意
给出一个长度为 n n n 的一维数组 a r r arr arr,将 a r r arr arr 中的每个元素循环向右移动 m m m 个位置。更严格一点的定义是:将原本在位置 i i i 处的元素移动至 ( i + m ) m o d n (i+m)\bmod n (i+m)modn 处。
比如,输入为
(
1
,
2
,
3
,
4
,
5
,
6
)
(1,2,3,4,5,6)
(1,2,3,4,5,6),
m
=
2
m=2
m=2,操作结果如下图示:
方法一
首先明确一件事情,根据取模运算的分配律可得
(
i
+
m
)
m
o
d
n
=
(
i
m
o
d
n
+
m
m
o
d
n
)
m
o
d
n
=
(
i
+
m
m
o
d
n
)
m
o
d
n
\begin{aligned} (i+m)\bmod n &= (i\bmod n + m\bmod n)\bmod n \\ &= (i + m\bmod n)\bmod n \end{aligned}
(i+m)modn=(imodn+mmodn)modn=(i+mmodn)modn
从操作上来讲,上述式子也很好理解——循环向右移动 n n n 个位置,必然回到起始位置。
因此,在移动之前先进行 m = m m o d n m=m\bmod n m=mmodn,避免做无用功。现在 m ∈ [ 0 , n ) m∈[0,n) m∈[0,n) 了。
通过观察不难发现,操作完成后:
- 后 m m m 个元素被移到了前 m m m 个位置。
- 前 n − m n-m n−m个元素被移到了后 n − m n-m n−m 个位置。
于是,先将整个数组翻转一次。
如上图所示,翻转之后,原先的最后
m
m
m 个元素的确移到 前
n
−
m
n-m
n−m 个元素之前了,不过次序是反的。为了调整次序,可将区间
[
0
,
m
)
[0,m)
[0,m) 和
[
m
,
n
)
[m,n)
[m,n) 再各进行一次翻转。
整个翻转过程如下图所示:
《数组入门指北》中提到过翻转的套路:将前
n
2
\frac{n}{2}
2n 个元素与后
n
2
\frac{n}{2}
2n 元素交换位置。
因为本题中需要进行三次翻转:
- 整个数组翻转一次
- 前 m m m 个元素翻转一次
- 后 n − m n-m n−m 个元素翻转一次
所以我们可以将翻转的代码封装为一个函数,这样代码更加整洁(说不定你的面试官有代码洁癖呢)。代码如下:
class Solution {
public:
void reverse(vector<int> &a, int L, int R) {
for (int i = (L+R)/2; i >= L ; i--) {
int tmp = a[i];
a[i] = a[R+L-i];
a[R+L-i] = tmp;
}
}
vector<int> solve(int n, int m, vector<int>& a) {
m %= n;
if (m == 0) {
return a;
}
reverse(a, 0, n-1);
reverse(a, 0, m-1);
reverse(a, m, n-1);
return a;
}
};
方法二
方法一算是比较取巧的做法了,不是很直观。接下来讲解一下模拟循环右移的做法。
考虑有 x x x 为满足 x ∗ m m o d n = 0 x*m \bmod n = 0 x∗mmodn=0 的最小正整数,不难发现 1 ≤ x ≤ n 1\le x \le n 1≤x≤n。可将 n n n 个元素拆分为 n x \frac{n}{x} xn 个环,每个环有 x x x 个元素。
上面这段看不懂也没关系,后面有机会讲数学部分的时候,我们再讨论。
上面这段的目的是想说,可将 n n n 个元素划分为若干个环,属于同一个环的相邻的下标 p p p 和 q q q 满足 ( p + m ) m o d n = q (p+m)\bmod n = q (p+m)modn=q,换言之, p p p 处的数据会移动到 q q q 处。
于是,我们可将整个数组视作若干个独立的等长的子序列,只要完成所有子序列的位移,就完成了整个数组的位移。
如下图所示,要对一个长度为 8 的数组,向右移动两次。首先,将数组分成两个子序列。相同颜色的数据属于同一个子序列。移动过程,如下图所示:
整个过程,可总结为:
- 首先,找到 x x x。
- 枚举前 n x \frac{n}{x} xn 个位置,相当于枚举每个环中的最小的位置。
- 移动每个环中的数据。
代码如下:
class Solution {
public:
vector<int> solve(int n, int m, vector<int>& a) {
m %= n;
if (m == 0) {
return a;
}
int x = 1;
while (x*m%n != 0) {
x++;
}
for (int i = 0; i < n/x; i++) {
if (a[i] < 0) { continue; }
for (int j = (i+m)%n; j != i; j = (j+m)%n) {
int tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
return a;
}
};