[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]

Posted 杗玉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[1806] LeetCode 刷题笔记: 还原排列的最少操作步数 [M]相关的知识,希望对你有一定的参考价值。

LeetCode 刷题笔记

[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)\\)

其中原排列中的第 0n−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)))\\)等等,那么我们可以推出:

\\[g^k(i) \\equiv 2^ki \\mod (n-1) \\]

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

\\(i=1\\) 时,我们有

\\[g^k(1) \\equiv 2^k \\equiv 1 \\mod (n-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 是奇数,2n-1 互质,那么根据「欧拉定理」:

\\[2^\\varphi(n-1) \\equiv 1 \\mod (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]的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode刷题笔记-数据结构-day20

LeetCode刷题笔记-数据结构-day20

LeetCode刷题笔记-数据结构-day20

LeetCode Java刷题笔记—83. 删除排序链表中的重复元素

Leetcode刷题笔记-动态规划

LeetCode刷题笔记-数据结构-day19