刷题笔记-小R的排列挑战
问题描述
小R有一个长度为 的排列,排列中的数字是 到 的整数。她可以进行如下操作:
- 操作:选择两个下标 和 满足 和 的奇偶性相同(即同为奇数或同为偶数),然后交换 和 。
她想通过最少的操作次数使得数组变为升序排列。如果无法通过上述操作达成目标,输出 。
解题分析
1. 可交换的位置
由于只能交换下标奇偶性相同的元素,我们可以将数组划分为两组:
- 奇数位置元素:下标为 的元素。
- 偶数位置元素:下标为 的元素。
在这种情况下,奇数位置的元素只能在奇数位置间进行交换,偶数位置的元素只能在偶数位置间进行交换。
2. 判断是否可行
要使数组最终升序排列,我们需要满足以下条件:
- 奇数位置的元素经过排序后,必须与目标排序后奇数位置的元素相同。
- 偶数位置的元素经过排序后,必须与目标排序后偶数位置的元素相同。
如果这两个条件不满足,则无法通过允许的操作将数组排序,输出 。
3. 计算最小交换次数
对于奇数位置和偶数位置的元素,我们分别计算将其排序所需的最小交换次数。这个问题可以转化为计算最小交换次数使数组有序,即求数组的最小交换环数。
最小交换次数的计算方法
- 记录元素及其原始位置:将元素和其在子数组中的位置进行配对。
- 排序配对数组:按照元素的值对配对数组进行排序。
- 计算交换环数:
- 初始化一个访问标记数组
visited,长度与子数组相同,初始为False。 - 遍历配对数组,如果当前位置已访问或元素位置已正确,跳过。
- 否则,计算交换环的长度,交换次数为环长度减一。
- 初始化一个访问标记数组
代码实现
def solution(n: int, a: list) -> int:
# 分别提取奇数位置和偶数位置的元素
odd_elements = [a[i] for i in range(0, n, 2)]
even_elements = [a[i] for i in range(1, n, 2)]
# 将原数组排序,得到目标有序数组
sorted_a = sorted(a)
# 分别提取目标有序数组中奇数位置和偶数位置的元素
sorted_odd_elements = [sorted_a[i] for i in range(0, n, 2)]
sorted_even_elements = [sorted_a[i] for i in range(1, n, 2)]
# 判断是否可以通过允许的操作将数组排序
if sorted(odd_elements) != sorted_odd_elements or sorted(even_elements) != sorted_even_elements:
return -1
# 分别计算奇数位置和偶数位置的最小交换次数
swaps_odd = count_min_swaps(odd_elements)
swaps_even = count_min_swaps(even_elements)
# 返回总的最小交换次数
return swaps_odd + swaps_even
def count_min_swaps(arr):
n = len(arr)
# 将元素与其在子数组中的原始位置进行配对
arrpos = list(enumerate(arr))
# 按照元素值进行排序
arrpos.sort(key=lambda it: it[1])
# 初始化访问标记数组
visited = [False] * n
swaps = 0
for i in range(n):
# 如果已访问或元素已在正确位置,跳过
if visited[i] or arrpos[i][0] == i:
continue
cycle_size = 0
j = i
# 计算交换环的长度
while not visited[j]:
visited[j] = True
j = arrpos[j][0]
cycle_size += 1
# 累加交换次数
if cycle_size > 0:
swaps += (cycle_size - 1)
return swaps
if __name__ == '__main__':
print(solution(5, [1, 4, 5, 2, 3]) == 2)
print(solution(4, [4, 3, 2, 1]) == -1)
print(solution(6, [2, 4, 6, 1, 3, 5]) == -1)
代码详解
主函数 solution
-
提取奇偶位置元素:
odd_elements = [a[i] for i in range(0, n, 2)] even_elements = [a[i] for i in range(1, n, 2)] -
获取目标排序后的奇偶位置元素:
sorted_a = sorted(a) sorted_odd_elements = [sorted_a[i] for i in range(0, n, 2)] sorted_even_elements = [sorted_a[i] for i in range(1, n, 2)] -
判断是否可行:
if sorted(odd_elements) != sorted_odd_elements or sorted(even_elements) != sorted_even_elements: return -1 -
计算最小交换次数:
swaps_odd = count_min_swaps(odd_elements) swaps_even = count_min_swaps(even_elements) return swaps_odd + swaps_even
辅助函数 count_min_swaps
-
配对元素和原始位置:
arrpos = list(enumerate(arr)) -
按照元素值排序:
arrpos.sort(key=lambda it: it[1]) -
初始化访问标记和交换次数:
visited = [False] * n swaps = 0 -
遍历计算交换环:
for i in range(n): if visited[i] or arrpos[i][0] == i: continue cycle_size = 0 j = i while not visited[j]: visited[j] = True j = arrpos[j][0] cycle_size += 1 if cycle_size > 0: swaps += (cycle_size - 1)
总结
这道题考察了对数组操作限制下的排序问题,通过将问题分解为奇数位置和偶数位置的最小交换问题,我们可以有效地判断是否可行并计算最小交换次数。
关键点在于:
- 理解交换操作的限制,明确只能在同奇偶性的位置间交换。
- 分别处理奇数位置和偶数位置的元素,判断可行性。
- 使用最小交换环的计算方法,求解最小交换次数。