Medium

题目描述

给你一个偶数 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 是偶数
  • 操作次数不会超过 n
  • 数字足够小,可以使用暴力解法

解题思路

这道题可以用两种思路来解决:

方法一:模拟法(推荐) 直接模拟整个过程,按照题目给定的规则重复执行操作,直到排列回到初始状态。每次操作创建新数组,按规则填充:偶数位置取前半部分元素,奇数位置取后半部分元素。计算操作次数直到排列复原。

方法二:数学优化法 观察到这个操作本质上是一个置换(permutation),我们可以只跟踪关键位置的变化。具体来说,我们只需要跟踪位置 1 的元素经过多少次操作后回到位置 1。因为这是一个循环置换,当位置 1 的元素回到原位时,整个排列就恢复了。

根据变换规则:

  • 偶数位置 i 的元素来自 perm[i/2]
  • 奇数位置 i 的元素来自 perm[n/2 + (i-1)/2]

我们可以推导出位置的映射关系,然后计算循环长度。

两种方法时间复杂度都可以接受,但模拟法更容易理解和实现,且题目提示操作次数不超过 n,所以推荐使用模拟法。

代码实现

class Solution {
public:
    int reinitializePermutation(int n) {
        vector<int> perm(n);
        for (int i = 0; i < n; i++) {
            perm[i] = i;
        }
        
        vector<int> original = perm;
        int operations = 0;
        
        do {
            vector<int> arr(n);
            for (int i = 0; i < n; i++) {
                if (i % 2 == 0) {
                    arr[i] = perm[i / 2];
                } else {
                    arr[i] = perm[n / 2 + (i - 1) / 2];
                }
            }
            perm = arr;
            operations++;
        } while (perm != original);
        
        return operations;
    }
};
class Solution:
    def reinitializePermutation(self, n: int) -> int:
        perm = list(range(n))
        original = perm[:]
        operations = 0
        
        while True:
            arr = [0] * n
            for i in range(n):
                if i % 2 == 0:
                    arr[i] = perm[i // 2]
                else:
                    arr[i] = perm[n // 2 + (i - 1) // 2]
            
            perm = arr
            operations += 1
            
            if perm == original:
                break
                
        return operations
public class Solution {
    public int ReinitializePermutation(int n) {
        int[] perm = new int[n];
        for (int i = 0; i < n; i++) {
            perm[i] = i;
        }
        
        int[] original = new int[n];
        Array.Copy(perm, original, n);
        int operations = 0;
        
        do {
            int[] arr = new int[n];
            for (int i = 0; i < n; i++) {
                if (i % 2 == 0) {
                    arr[i] = perm[i / 2];
                } else {
                    arr[i] = perm[n / 2 + (i - 1) / 2];
                }
            }
            perm = arr;
            operations++;
        } while (!perm.SequenceEqual(original));
        
        return operations;
    }
}
var reinitializePermutation = function(n) {
    let perm = Array.from({length: n}, (_, i) => i);
    const original = [...perm];
    let operations = 0;
    
    do {
        let arr = new Array(n);
        for (let i = 0; i < n; i++) {
            if (i % 2 === 0) {
                arr[i] = perm[i / 2];
            } else {
                arr[i] = perm[n / 2 + (i - 1) / 2];
            }
        }
        perm = arr;
        operations++;
    } while (!perm.every((val, idx) => val === original[idx]));
    
    return operations;
};

复杂度分析

复杂度类型模拟法
时间复杂度O(n²)
空间复杂度O(n)

说明:

  • 时间复杂度:最多需要 n 次操作(根据题目提示),每次操作需要 O(n) 时间创建新数组,所以总时间复杂度为 O(n²)
  • 空间复杂度:需要 O(n) 空间存储原始排列和临时数组