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

127 阅读4分钟

题解:小R的排列挑战

问题描述

小R有一个长度为 n 的排列,包含数字 1n。她可以选择两个下标奇偶性相同的数进行交换(即下标同为奇数或偶数),目标是通过最少的交换次数将数组变为升序排列。

如果无法通过上述规则将数组排序,则返回 -1;否则,返回最小的交换次数。


解题思路

1. 条件分析:奇偶限制的影响

由于交换只能在下标奇偶性相同的元素间进行,因此每个元素只能在其对应的奇偶性子序列内移动。假设目标数组是 [1, 2, ..., n],如果某个元素的目标位置和其当前所在位置的奇偶性不一致,则它无法移动到目标位置。例如:

  • 若奇数位置上应为偶数,但当前是奇数,则无法将其移到正确位置。
  • 类似地,偶数位置上的奇偶性冲突也会导致问题。

因此,需要检查当前数组是否存在奇偶性不匹配的情况

  • 遍历数组,若发现某个位置 i 的元素 a[i] 与目标位置 i 的奇偶性不同,则输出 -1

2. 转化问题:寻找最小交换次数

在确定可以通过合法操作排序后,问题转化为求最少交换次数。这可以通过环分解的思想来解决:

  1. 构建映射关系
    每个元素从其当前位置到目标位置形成一个映射。例如,元素 a[i] 在目标位置应为 i + 1,我们可以将其视为一个图的边:i → a[i]

  2. 环的性质
    映射形成一个或多个闭环。每个环表示一组元素,它们可以通过彼此交换排序完成。例如:

    • 一个大小为 k 的环需要至少 k - 1 次交换才能完成排序。
    • 对所有环的最小交换次数为: 最小交换次数=n−环的数量\text{最小交换次数} = n - \text{环的数量}
  3. 高效求解环数量
    为了快速计算环的数量,可以使用并查集:

    • 初始化每个位置为独立集合。
    • 如果两个位置之间可以通过合法交换连接,则将它们合并到同一个集合。
    • 最终,每个独立集合对应一个环。

3. 算法实现

以下是实现细节:

  1. 检查奇偶性不匹配:

    • 遍历数组,验证每个元素 a[i] 的奇偶性是否与其目标位置 i 一致。
    • 若发现不匹配,直接返回 -1
  2. 使用并查集求解环数量:

    • 构建并查集,将可以交换的元素连接起来。
    • 统计并查集中独立集合的数量,计算最小交换次数。

Python 代码实现

class UnionFind:
    def __init__(self, size):
        self.parent = list(range(size))  # 每个节点的父节点
        self.rank = [1] * size           # 用于路径压缩的秩
        self.size = [1] * size           # 每个集合的大小

    def find(self, p):
        if self.parent[p] != p:
            self.parent[p] = self.find(self.parent[p])  # 路径压缩
        return self.parent[p]

    def union(self, p, q):
        rootP = self.find(p)
        rootQ = self.find(q)
        if rootP != rootQ:
            # 按秩合并,保持并查集平衡
            if self.rank[rootP] > self.rank[rootQ]:
                self.parent[rootQ] = rootP
                self.size[rootP] += self.size[rootQ]
            elif self.rank[rootP] < self.rank[rootQ]:
                self.parent[rootP] = rootQ
                self.size[rootQ] += self.size[rootP]
            else:
                self.parent[rootQ] = rootP
                self.size[rootP] += self.size[rootQ]
                self.rank[rootP] += 1

    def get_size(self, p):
        root = self.find(p)
        return self.size[root]

def min_swaps_to_sort(n, a):
    # 检查奇偶性是否匹配
    for i in range(n):
        if a[i] % 2 != (i + 1) % 2:
            return -1

    # 初始化并查集
    uf = UnionFind(n + 1)
    for i in range(n):
        uf.union(i + 1, a[i])  # 建立位置到目标位置的映射

    # 统计连通分量数量
    components = 0
    for i in range(1, n + 1):
        if uf.find(i) == i:  # 每个独立集合的根节点
            components += 1

    # 计算最小交换次数
    return n - components

测试样例

# 样例1
print(min_swaps_to_sort(5, [1, 4, 5, 2, 3]))  # 输出:2

# 样例2
print(min_swaps_to_sort(4, [4, 3, 2, 1]))     # 输出:-1

# 样例3
print(min_swaps_to_sort(6, [2, 4, 6, 1, 3, 5]))  # 输出:-1

复杂度分析

  1. 奇偶性检查O(n)O(n)
    遍历数组检查奇偶性是否匹配。
  2. 并查集构建O(nα(n))O(n \cdot \alpha(n))
    并查集的合并与查找操作复杂度为近似常数 α(n)\alpha(n)(阿克曼函数的反函数)。
  3. 统计环数量O(n)O(n)
    遍历所有节点,统计独立集合数量。

总复杂度:O(nα(n))O(n \cdot \alpha(n)),接近线性。


总结

本题通过将排序问题转化为图中的环分解问题,高效地计算了最少交换次数。关键在于通过并查集构建交换关系,并利用奇偶性验证确保可行性。最终算法实现了清晰的逻辑和较优的性能,适用于 ICPC 等竞赛场景。