[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]
[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]
题目描述
给你一个偶数 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 回到排列初始值,至少需要执行多少步操作?返回最小的 非零 操作步数。
题解参考
直接模拟
题目要求,一步操作中,对于每个索引 ii,变换规则如下:
如果 i 为偶数,那么 arr[i] = perm[i / 2];
如果 i 为奇数,那么 arr[i] = permp[n / 2 + (i-1) / 2];
然后将 arr 赋值给 perm。
我们假设初始序列 perm=[0,1,2,...,n−1],按照题目上述要求的变换规则进行模拟,直到 perm 重新变回为序列 [0,1,2,⋯,n−1] 为止。每次将 perm 按照上述规则变化产生数组 arr,并将 arr 赋给 perm,然后我们检测 perm 是否回到原始状态并计数,如果回到原始状态则中止变换,否则继续变换。
Algorithm [ 还原排列的最少操作步数 : 直接模拟]
Input: { number /* You are given an even integer n */ }
Output: { Return the minimum non-zero number of operations you need to perform on perm to return the permutation to its initial value. }
-------------------------------
/* You are given an even integer n. You initially have a permutation perm of size n where perm[i] == i (0-indexed). */
Function SimulationMethod
perm[n:0], target[n:0] /* Init the two array, n items */
step <- 0
While True
arr[n:0] /* Init the temp array */
For i <- 0 to n
If i & 1
arr[i] <- perm[n /2 + (i - 1) / 2]
Else
arr[i] <- perm[i /2]
EndIf
EndFor
perm = CppMove(arr)
step++ /* increase the step */
If target == perm
Break
EndIf
EndWhile
Return step
EndFunction
复杂度分析
-
时间复杂度:\(O(n^2)\),其中 \(n\) 表示给定的元素。根据方法二的推论可以知道最多需要经过 \(n\) 次变换即可回到初始状态,每次变换需要的时间复杂度为 \(O(n)\),因此总的时间复杂度为 \(O(n^2)\)。
-
空间复杂度:\(O(n)\),其中 \(n\) 表示给定的元素。我们需要存储每次变换中的过程变量,需要的空间为 \(O(n)\)。
数学
我们需要观察一下原排列中对应的索引变换关系。对于原排列中第 ``i 个元素,设 g(i) 为进行一次操作后,该元素的新的下标,题目转化规则如下:
-
如果
g(i)为偶数,那么 \(arr[g(i)]=perm[\frac{g(i)}{2}]\),令 \(x = \frac{g(i)}{2}\),则该等式转换为 \(arr[2x] = perm[x]\),此时 \(x\in[0,\frac{n-1}{2}]\); -
如果
g(i)为奇数,那么 \(arr[g(i)] = perm[\frac{n}{2} + \frac{g(i)-1}{2}]\),令 \(x = \frac{n}{2} + \frac{g(i)-1}{2}\),则该等式转换为 \(arr[2x-n-1] = perm[x]\),此时 \(x \in[\frac{n+1}{2},\frac{n}{2}]\);
因此根据题目的转换规则可以得到以下对应关系:
-
当 \(0\le i < \frac{n}{2}\) 时,此时 \(g(i) = 2i\);
-
当 \(\frac{n}{2} \le i < n\)时,此时 \(g(i) = 2i-(n-1)\);
其中原排列中的第 0 和 n−1 个元素的下标不会变换,我们无需进行考虑。 对于其余元素 \(i \in [1, n-1)\),上述两个等式可以转换为对 \(n−1\) 取模,等式可以转换为 \(g(i) \equiv 2i \mod {(n-1)}\)。
记 \(g^k(i)\)表示原排列 perm 中第 i 个元素经过 k 次变换后的下标,即 \(g^2(i) = g(g(i))\), \(g^3(i) = g(g(g(i)))\)等等,那么我们可以推出:
为了让排列还原到初始值,原数组中索引为 i 的元素经过多次变换后索引变回 i,此时必须有:\(g^k(i) \equiv 2^ki \equiv i \mod {(n-1)}\)。我们只需要找到最小的 k,使得上述等式对于 \(i\in[1,n-1)\) 均成立,此时的 k 即为所求的最小变换次数。
当 \(i=1\) 时,我们有
如果存在 k 满足上式,那么将上式两侧同乘 i,得到 \(g^k(i) \equiv 2^ki \equiv i \mod {(n-1)}\) 即对于 \(i \in [1, n-1)\) 恒成立。因此原题等价于寻找最小的 k,使得 \(2^k \equiv 1 \mod {(n-1)}\)。
由于 n 为偶数,则 n-1 是奇数,2 和 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)}\) 即可。
Algorithm [ 还原排列的最少操作步数 : 数学 ]
Input: { number /* You are given an even integer n */ }
Output: { Return the minimum non-zero number of operations you need to perform on perm to return the permutation to its initial value. }
-------------------------------
/* You are given an even integer n. You initially have a permutation perm of size n where perm[i] == i (0-indexed). */
Function MathMethod
If 2 == number
Return 1
EndIf
step <- 1, pow2 <- 2
While 1 != pow2
step++ /* increase the step */
pow2 <- pow2 * 2 Mod (n - 1)
EndWhale
Retrun step
EndFunction
复杂度分析
-
时间复杂度:\(O(n)\),其中 \(n\) 表示给定的元素。根据推论可以知道最多需要进行计算的次数不超过 \(n\),因此时间复杂度为 \(O(n)\)。
-
空间复杂度:\(O(1)\)。
参考题解
C/C++ 的相关参考
C/C++ 的相关的代码
class Solution {
struct Simulation {};
struct Math {};
public:
int reinitializePermutation(int n) {
return reinitializePermutationImpl(Math(), n);
}
int reinitializePermutationImpl(Simulation, 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;
}
int reinitializePermutationImpl(Math, int n) {
if (n == 2) {
return 1;
}
int step = 1, pow2 = 2;
while (pow2 != 1) {
step++;
pow2 = pow2 * 2 % (n - 1);
}
return step;
}
};
Rust 的相关参考
Rust 的相关的代码
impl Math for Solution {}
trait Math {
fn reinitialize_permutation(n: i32) -> i32 {
let (mut cnt, mut idx) = (1, n / 2);
while idx != 1 {
idx = if idx & 1 == 1 { n / 2 + (idx - 1) / 2 } else { idx / 2 };
cnt += 1;
}
cnt
}
}

[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]
浙公网安备 33010602011771号