【1806. 还原排列的最少操作步数】
创始人
2024-05-09 03:59:32
0

来源:力扣(LeetCode)

描述:

给你一个偶数 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] = perm[ i / 2 ];
  • 如果 i 为奇数,那么 arr[i] = perm[ n / 2+ (i − 1) / 2 ];

然后将 arr 赋值给 perm。

我们假设初始序列 perm = [0, 1, 2, ⋯, n − 1],按照题目上述要求的变换规则进行模拟,直到 perm 重新变回为序列 [0, 1, 2, ⋯, n − 1] 为止。每次将 perm 按照上述规则变化产生数组 arr,并将 arr 赋给 perm,然后我们检测 perm 是否回到原始状态并计数,如果回到原始状态则中止变换,否则继续变换。

代码:

class Solution {
public:int reinitializePermutation(int n) {vector perm(n), target(n);iota(perm.begin(), perm.end(), 0);iota(target.begin(), target.end(), 0);int step = 0;while (true) {vector 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;}
};

执行用时:32 ms, 在所有 C++ 提交中击败了19.33%的用户
内存消耗:25.2 MB, 在所有 C++ 提交中击败了5.04%的用户
复杂度分析
时间复杂度:O(n2)O,其中 n 表示给定的元素。根据方法二的推论可以知道最多需要经过 n 次变换即可回到初始状态,每次变换需要的时间复杂度为 O(n),因此总的时间复杂度为 O(n2)。
空间复杂度:O(n),其中 n 表示给定的元素。我们需要存储每次变换中的过程变量,需要的空间为O(n)。

方法二:数学

思路与算法

我们需要观察一下原排列中对应的索引变换关系。对于原排列中第 i 个元素,设 g(i) 为进行一次操作后,该元素的新的下标,题目转化规则如下:

  • 如果 g(i) 为偶数,那么 arr[g(i)] = perm[ g(i) / 2 ],令 x = g(i) / 2 ,则该等式转换为 arr[2x] = perm[x],此时 x ∈ [0, (n − 1) / 2 ];

  • 如果 g(i) 为奇数,那么 arr[g(i)] = perm[ n / 2 + (g(i) − 1) / 2 ],令 x = n / 2 + (g(i) − 1) / 2 ,则该等式转换为 arr[2x − n − 1] = perm[x],此时 x ∈ [ (n + 1) / 2 , n / 2 ];

因此根据题目的转换规则可以得到以下对应关系:

  • 当 0 ≤ i< (n / 2) 时,此时 g(i) = 2i;

  • 当 (n / 2) ≤ i < n 时,此时 g(i) = 2i - (n - 1);

其中原排列中的第 0 和 n - 1 个元素的下标不会变换,我们无需进行考虑。 对于其余元素 i ∈ [1, n − 1),上述两个等式可以转换为对 n - 1 取模,等式可以转换为 g(i) ≡ 2i(mod(n−1))。

记 gk(i) 表示原排列 perm 中第 i 个元素经过 k 次变换后的下标,即 g2(i) = g(g(i)), g3(i) = g(g(g(i))) 等等,那么我们可以推出:

g^k^(i) ≡ 2^k^i (mod(n − 1))

为了让排列还原到初始值,原数组中索引为 i 的元素经过多次变换后索引变回 i,此时必须有:gk(i) ≡ 2ki ≡ i(mod(n − 1))。我们只需要找到最小的 k,使得上述等式对于 i ∈ [1, n − 1) 均成立,此时的 kk 即为所求的最小变换次数。

当 i = 1 时,我们有

g^k^(i) ≡ 2^k^ ≡ 1(mod(n − 1))

如果存在 k 满足上式,那么将上式两侧同乘 i,得到 gk(i) ≡ 2ki (mod(n − 1)) 即对于 i ∈ [1, n − 1) 恒成立。因此原题等价于寻找最小的 k,使得 2k ≡ 1(mod(n − 1))。

由于 n 为偶数,则 n − 1 是奇数,2 和 n - 1 互质,那么根据「欧拉定理」:

2^φ(n−1)^ ≡ 1(mod(n − 1))

即 k = φ(n − 1) 一定是一个解,其中 φ 为「欧拉函数」。因此,最小的 k 一定小于等于 φ(n−1),而欧拉函数 φ(n − 1) ≤ n − 1,因此可以知道 k ≤ n−1 的,因此总的时间复杂度不超过 O(n)。

根据上述推论,我们直接模拟即找到最小的 k 使得满足 2k ≡ 1(mod(n − 1)) 即可。

代码:

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;}
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.7 MB, 在所有 C++ 提交中击败了87.40%的用户
复杂度分析
时间复杂度:O(n),其中 n 表示给定的元素。根据推论可以知道最多需要进行计算的次数不超过 n,因此时间复杂度为 O(n)。
空间复杂度:O(1)。
author:LeetCode-Solution

上一篇:C语言-扫雷

下一篇:前缀和算法

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...