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 <= 1000n是偶数- 操作次数不会超过
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) 空间存储原始排列和临时数组