给你一个偶数 n
,已知存在一个长度为 n
的排列 perm
,其中 perm[i] == i
(下标 从 0 开始
计数)。
一步操作中,你将创建一个新数组 arr
,对于每个 i
:
- 如果
i % 2 == 0
,那么 arr[i] = perm[i / 2]
- 如果
i % 2 == 1
,那么 arr[i] = perm[n / 2 + (i - 1) / 2]
然后将 arr
赋值给 perm
。
要想使 perm
回到排列初始值,至少需要执行多少步操作?返回最小的 非零 操作步数。
示例 1:
**输入:** n = 2
**输出:** 1
**解释:** 最初,perm = [0,1]
第 1 步操作后,perm = [0,1]
所以,仅需执行 1 步操作
示例 2:
**输入:** n = 4
**输出:** 2
**解释:** 最初,perm = [0,1,2,3]
第 1 步操作后,perm = [0,2,1,3]
第 2 步操作后,perm = [0,1,2,3]
所以,仅需执行 2 步操作
示例 3:
**输入:** n = 6
**输出:** 4
提示:
2 <= n <= 1000
n
是一个偶数
方法一:直接模拟
思路与算法
题目要求,一步操作中,对于每个索引 i,变换规则如下:
- 如果 i 为偶数,那么 arr}[i] = \textit{perm}[\dfrac{i}{2}];
- 如果 i 为奇数,那么 arr}[i] = \textit{perm}[\dfrac{n}{2} + \dfrac{i-1/2}];
然后将 arr 赋值给 perm。
我们假设初始序列 perm} = [0,1,2,\cdots,n-1],按照题目上述要求的变换规则进行模拟,直到 perm 重新变回为序列 [0,1,2,\cdots,n-1] 为止。每次将 perm 按照上述规则变化产生数组 arr,并将 arr 赋给 perm,然后我们检测 perm 是否回到原始状态并计数,如果回到原始状态则中止变换,否则继续变换。
代码
[sol1-Python3]1 2 3 4 5 6 7 8 9 10
| class Solution: def reinitializePermutation(self, n: int) -> int: perm = list(range(n)) target = perm.copy() step = 0 while True: step += 1 perm = [perm[n // 2 + (i - 1) // 2] if i % 2 else perm[i // 2] for i in range(n)] if perm == target: return step
|
[sol1-C++]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
| class Solution { public: int reinitializePermutation(int n) { vector<int> perm(n), target(n); iota(perm.begin(), perm.end(), 0); iota(target.begin(), target.end(), 0); int step = 0; while (true) { vector<int> arr(n); for (int i = 0; i < n; i++) { if (i & 1) { arr[i] = perm[n / 2 + (i - 1) / 2]; } else { arr[i] = perm[i / 2]; } } perm = move(arr); step++; if (perm == target) { break; } } return step; } };
|
[sol1-Java]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
| class Solution { public int reinitializePermutation(int n) { int[] perm = new int[n]; int[] target = new int[n]; for (int i = 0; i < n; i++) { perm[i] = i; target[i] = i; } int step = 0; while (true) { int[] arr = new int[n]; for (int i = 0; i < n; i++) { if ((i & 1) != 0) { arr[i] = perm[n / 2 + (i - 1) / 2]; } else { arr[i] = perm[i / 2]; } } perm = arr; step++; if (Arrays.equals(perm, target)) { break; } } return step; } }
|
[sol1-C#]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
| public class Solution { public int ReinitializePermutation(int n) { int[] perm = new int[n]; int[] target = new int[n]; for (int i = 0; i < n; i++) { perm[i] = i; target[i] = i; } int step = 0; while (true) { int[] arr = new int[n]; for (int i = 0; i < n; i++) { if ((i & 1) != 0) { arr[i] = perm[n / 2 + (i - 1) / 2]; } else { arr[i] = perm[i / 2]; } } perm = arr; step++; if (Enumerable.SequenceEqual(perm, target)) { break; } } return step; } }
|
[sol1-C]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
| int reinitializePermutation(int n) { int perm[n], arr[n], target[n]; for (int i = 0; i < n; i++) { perm[i] = i; target[i] = i; } int step = 0; int *pArr = arr, *pPerm = perm; while (true) { for (int i = 0; i < n; i++) { if (i & 1) { pArr[i] = pPerm[n / 2 + (i - 1) / 2]; } else { pArr[i] = pPerm[i / 2]; } } int *tmp = pArr; pArr = pPerm; pPerm = tmp; step++; if (memcmp(pPerm, target, sizeof(int) * n) == 0) { break; } } return step; }
|
[sol1-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var reinitializePermutation = function(n) { let perm = new Array(n).fill(0).map((_, i) => i); const target = new Array(n).fill(0).map((_, i) => i); let step = 0; while (true) { const arr = new Array(n).fill(0); for (let i = 0; i < n; i++) { if ((i & 1) !== 0) { arr[i] = perm[Math.floor(n / 2) + Math.floor((i - 1) / 2)]; } else { arr[i] = perm[Math.floor(i / 2)]; } } perm = arr; step++; if (perm.toString() === target.toString()) { break; } } return step; };
|
[sol1-Golang]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
| func reinitializePermutation(n int) (step int) { target := make([]int, n) for i := range target { target[i] = i } perm := append([]int(nil), target...) for { step++ arr := make([]int, n) for i := range arr { if i%2 == 0 { arr[i] = perm[i/2] } else { arr[i] = perm[n/2+i/2] } } perm = arr if equal(perm, target) { return } } }
func equal(a, b []int) bool { for i, x := range a { if x != b[i] { return false } } return true }
|
复杂度分析
方法二:数学
思路与算法
我们需要观察一下原排列中对应的索引变换关系。对于原排列中第 i 个元素,设 g(i) 为进行一次操作后,该元素的新的下标,题目转化规则如下:
- 如果 g(i) 为偶数,那么 arr}[g(i)] = \textit{perm}[\dfrac{g(i)}{2}],令 x = \dfrac{g(i)}{2,则该等式转换为 arr}[2x] = \textit{perm}[x],此时 x\in[0,\dfrac{n-1/2}];
- 如果 g(i) 为奇数,那么 arr}[g(i)] = \textit{perm}[\dfrac{n}{2} + \dfrac{g(i)-1/2}],令 x = \dfrac{n}{2} + \dfrac{g(i)-1/2,则该等式转换为 arr}[2x-n-1] = \textit{perm}[x],此时 x \in[\dfrac{n+1/2},\dfrac{n}{2}];
因此根据题目的转换规则可以得到以下对应关系:
- 当 0\le i < \dfrac{n}{2 时,此时 g(i) = 2i;
- 当 \dfrac{n}{2} \le i < n 时,此时 g(i) = 2i-(n-1);
其中原排列中的第 0 和 n-1 个元素的下标不会变换,我们无需进行考虑。 对于其余元素 i \in [1, n-1),上述两个等式可以转换为对 n-1 取模,等式可以转换为 g(i) \equiv 2i \pmod {(n-1)。
记 g^k(i) 表示原排列 perm 中第 i 个元素经过 k 次变换后的下标,即 g^2(i) = g(g(i)), g^3(i) = g(g(g(i))) 等等,那么我们可以推出:
g^k(i) \equiv 2^ki \pmod {(n-1)
为了让排列还原到初始值,原数组中索引为 i 的元素经过多次变换后索引变回 i,此时必须有:g^k(i) \equiv 2^ki \equiv i \pmod {(n-1)。我们只需要找到最小的 k,使得上述等式对于 i\in[1,n-1) 均成立,此时的 k 即为所求的最小变换次数。
当 i=1 时,我们有
g^k(1) \equiv 2^k \equiv 1 \pmod {(n-1)
如果存在 k 满足上式,那么将上式两侧同乘 i,得到 g^k(i) \equiv 2^ki \equiv i \pmod {(n-1) 即对于 i \in [1, n-1) 恒成立。因此原题等价于寻找最小的 k,使得 2^k \equiv 1 \pmod {(n-1)。
由于 n 为偶数,则 n-1 是奇数,2 和 n-1 互质,那么根据「欧拉定理 」:
2^{\varphi(n-1)} \equiv 1 \pmod {(n-1)}
即 k=\varphi(n-1) 一定是一个解,其中 \varphi 为「欧拉函数 」。因此,最小的 k 一定小于等于 \varphi(n-1),而欧拉函数 \varphi(n-1) \le n -1,因此可以知道 k \le n - 1 的,因此总的时间复杂度不超过 O(n)。
根据上述推论,我们直接模拟即找到最小的 k 使得满足 2^k \equiv 1 \pmod {(n-1) 即可。
代码
[sol2-Python3]1 2 3 4 5 6 7 8 9
| class Solution: def reinitializePermutation(self, n: int) -> int: if n == 2: return 1 step, pow2 = 1, 2 while pow2 != 1: step += 1 pow2 = pow2 * 2 % (n - 1) return step
|
[sol2-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int reinitializePermutation(int n) { if (n == 2) { return 1; } int step = 1, pow2 = 2; while (pow2 != 1) { step++; pow2 = pow2 * 2 % (n - 1); } return step; } };
|
[sol2-Java]1 2 3 4 5 6 7 8 9 10 11 12 13
| class Solution { public int reinitializePermutation(int n) { if (n == 2) { return 1; } int step = 1, pow2 = 2; while (pow2 != 1) { step++; pow2 = pow2 * 2 % (n - 1); } return step; } }
|
[sol2-C#]1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Solution { public int ReinitializePermutation(int n) { if (n == 2) { return 1; } int step = 1, pow2 = 2; while (pow2 != 1) { step++; pow2 = pow2 * 2 % (n - 1); } return step; } }
|
[sol2-C]1 2 3 4 5 6 7 8 9 10 11
| int reinitializePermutation(int n) { if (n == 2) { return 1; } int step = 1, pow2 = 2; while (pow2 != 1) { step++; pow2 = pow2 * 2 % (n - 1); } return step; }
|
[sol2-JavaScript]1 2 3 4 5 6 7 8 9 10 11
| var reinitializePermutation = function(n) { if (n === 2) { return 1; } let step = 1, pow2 = 2; while (pow2 !== 1) { step++; pow2 = pow2 * 2 % (n - 1); } return step; };
|
[sol2-Golang]1 2 3 4 5 6 7 8 9 10 11 12
| func reinitializePermutation(n int) int { if n == 2 { return 1 } step := 1 pow2 := 2 for pow2 != 1 { step++ pow2 = pow2 * 2 % (n - 1) } return step }
|
复杂度分析