刷题笔记-小R的排列挑战 | 豆包MarsCode AI刷题

74 阅读4分钟

刷题笔记-小R的排列挑战

问题描述

www.marscode.cn/practice/vk…

小R有一个长度为 nn 的排列,排列中的数字是 11nn 的整数。她可以进行如下操作:

  • 操作:选择两个下标 iijj 满足 iijj 的奇偶性相同(即同为奇数或同为偶数),然后交换 aia_iaja_j

她想通过最少的操作次数使得数组变为升序排列。如果无法通过上述操作达成目标,输出 1-1

解题分析

1. 可交换的位置

由于只能交换下标奇偶性相同的元素,我们可以将数组划分为两组:

  • 奇数位置元素:下标为 1,3,5,1, 3, 5, \dots 的元素。
  • 偶数位置元素:下标为 2,4,6,2, 4, 6, \dots 的元素。

在这种情况下,奇数位置的元素只能在奇数位置间进行交换,偶数位置的元素只能在偶数位置间进行交换。

2. 判断是否可行

要使数组最终升序排列,我们需要满足以下条件:

  • 奇数位置的元素经过排序后,必须与目标排序后奇数位置的元素相同。
  • 偶数位置的元素经过排序后,必须与目标排序后偶数位置的元素相同。

如果这两个条件不满足,则无法通过允许的操作将数组排序,输出 1-1

3. 计算最小交换次数

对于奇数位置和偶数位置的元素,我们分别计算将其排序所需的最小交换次数。这个问题可以转化为计算最小交换次数使数组有序,即求数组的最小交换环数。

最小交换次数的计算方法
  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

  1. 提取奇偶位置元素

    odd_elements = [a[i] for i in range(0, n, 2)]
    even_elements = [a[i] for i in range(1, n, 2)]
    
  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)]
    
  3. 判断是否可行

    if sorted(odd_elements) != sorted_odd_elements or sorted(even_elements) != sorted_even_elements:
        return -1
    
  4. 计算最小交换次数

    swaps_odd = count_min_swaps(odd_elements)
    swaps_even = count_min_swaps(even_elements)
    return swaps_odd + swaps_even
    

辅助函数 count_min_swaps

  1. 配对元素和原始位置

    arrpos = list(enumerate(arr))
    
  2. 按照元素值排序

    arrpos.sort(key=lambda it: it[1])
    
  3. 初始化访问标记和交换次数

    visited = [False] * n
    swaps = 0
    
  4. 遍历计算交换环

    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)
    

总结

这道题考察了对数组操作限制下的排序问题,通过将问题分解为奇数位置和偶数位置的最小交换问题,我们可以有效地判断是否可行并计算最小交换次数。

关键点在于:

  • 理解交换操作的限制,明确只能在同奇偶性的位置间交换。
  • 分别处理奇数位置和偶数位置的元素,判断可行性。
  • 使用最小交换环的计算方法,求解最小交换次数。