LeetCode 1806 | 最小操作步数让排列回到初始状态 — 数学与置换周期的巧妙应用
题目描述
给定一个偶数 n,存在一个长度为 n 的排列 perm,其中 perm[i] == i(下标从0开始)。
定义一个操作步骤:
- 新建数组
arr,对每个索引i:- 若
i是偶数,则arr[i] = perm[i / 2] - 若
i是奇数,则arr[i] = perm[n / 2 + (i - 1) / 2]
- 若
将 arr 赋值给 perm。
问题: 经过多少次该操作后,perm 才会回到初始状态?返回最小的非零操作步数。
解题分析
这个问题的关键在于理解操作过程实际上是对数组下标的一种置换(Permutation),即每次操作相当于对元素的索引位置进行重排。题目问的是,这个置换重复多少次后,整个数组才回到原始排列。
本质问题转化: 找到这个置换的周期(cycle length),即最小的 k 使得对所有 i,经过 k 次变换,i 回到原点。
置换函数定义
定义一个函数 f(i) 表示一次操作后,索引 i 变换后的位置:
def f(i):
if i % 2 == 0:
return i // 2
else:
return n // 2 + (i - 1) // 2
例如:
- 对偶数索引
i,新位置是i/2 - 对奇数索引
i,新位置是n/2 + (i - 1)/2
求置换周期的思路
排列经过操作后,每个元素的索引都发生了置换。一个排列可以拆解为若干个互不交叉的置换环(cycles)。最终排列恢复初始状态的条件是每个环上的元素都回到原位置。
因此问题转化为:
- 找出所有环的长度(循环节)
- 计算这些环长度的 最小公倍数(LCM)
这个最小公倍数即为让整体排列回到原状所需的最少操作次数。
详细算法步骤
- 初始化一个布尔数组
visited,标记索引是否已被访问。 - 遍历数组索引
i:- 如果未访问,开始从
i出发,沿着置换函数f走,直到回到i,计算环长度count。
- 如果未访问,开始从
- 将所有环的长度
count求最小公倍数,得到最终答案。
代码实现(Python)
import math
class Solution:
def reinitializePermutation(self, n: int) -> int:
def f(i):
if i % 2 == 0:
return i // 2
else:
return n // 2 + (i - 1) // 2
visited = [False] * n
lcm = 1
for i in range(n):
if not visited[i]:
count = 0
j = i
while not visited[j]:
visited[j] = True
j = f(j)
count += 1
if count > 0:
lcm = math.lcm(lcm, count)
return lcm
示例说明
示例 1:n = 2
初始 perm = [0, 1]
操作一次:
arr[0] = perm[0] = 0arr[1] = perm[1] = 1
perm 不变,周期为 1。
输出:1
示例 2:n = 4
初始:perm = [0, 1, 2, 3]
操作后:
arr[0] = perm[0] = 0arr[1] = perm[2] = 2arr[2] = perm[1] = 1arr[3] = perm[3] = 3
变为 [0, 2, 1, 3]
再操作一次:
arr[0] = perm[0] = 0arr[1] = perm[2] = 1arr[2] = perm[1] = 2arr[3] = perm[3] = 3
变回 [0, 1, 2, 3]
周期为 2。
复杂度分析
- 时间复杂度:
O(n log n)
主要因为每个元素访问一次,计算 LCM 可能涉及较大数的计算。 - 空间复杂度:
O(n)
用于visited数组。
进一步分析与比较
- 直接模拟:如果直接模拟每一步操作,直到恢复原状态,时间复杂度可能较高(最坏
O(n!))。 - 数学置换周期方法:通过分析置换结构,快速找到周期,效率大幅提升。
- 最小公倍数计算:对于分解出的每个环长,取 LCM 是数学保证周期正确性的关键。
小结
- 本题通过对置换周期的理解,将看似复杂的数组变换问题转化为数学问题。
- 利用函数映射构建置换,求环长,最后计算最小公倍数求周期。
- 这种思路不仅解决本题,也适用于类似置换周期求解问题。
598

被折叠的 条评论
为什么被折叠?



