[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]
Posted 杗玉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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[\\fracg(i)2]\\),令 \\(x = \\fracg(i)2\\),则该等式转换为 \\(arr[2x] = perm[x]\\),此时 \\(x\\in[0,\\fracn-12]\\); -
如果
g(i)
为奇数,那么 \\(arr[g(i)] = perm[\\fracn2 + \\fracg(i)-12]\\),令 \\(x = \\fracn2 + \\fracg(i)-12\\),则该等式转换为 \\(arr[2x-n-1] = perm[x]\\),此时 \\(x \\in[\\fracn+12,\\fracn2]\\);
因此根据题目的转换规则可以得到以下对应关系:
-
当 \\(0\\le i < \\fracn2\\) 时,此时 \\(g(i) = 2i\\);
-
当 \\(\\fracn2 \\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]的主要内容,如果未能解决你的问题,请参考以下文章