题解:小R的排列挑战
问题描述
小R有一个长度为 n 的排列,包含数字 1 到 n。她可以选择两个下标奇偶性相同的数进行交换(即下标同为奇数或偶数),目标是通过最少的交换次数将数组变为升序排列。
如果无法通过上述规则将数组排序,则返回 -1;否则,返回最小的交换次数。
解题思路
1. 条件分析:奇偶限制的影响
由于交换只能在下标奇偶性相同的元素间进行,因此每个元素只能在其对应的奇偶性子序列内移动。假设目标数组是 [1, 2, ..., n],如果某个元素的目标位置和其当前所在位置的奇偶性不一致,则它无法移动到目标位置。例如:
- 若奇数位置上应为偶数,但当前是奇数,则无法将其移到正确位置。
- 类似地,偶数位置上的奇偶性冲突也会导致问题。
因此,需要检查当前数组是否存在奇偶性不匹配的情况:
- 遍历数组,若发现某个位置
i的元素a[i]与目标位置i的奇偶性不同,则输出-1。
2. 转化问题:寻找最小交换次数
在确定可以通过合法操作排序后,问题转化为求最少交换次数。这可以通过环分解的思想来解决:
-
构建映射关系
每个元素从其当前位置到目标位置形成一个映射。例如,元素a[i]在目标位置应为i + 1,我们可以将其视为一个图的边:i → a[i]。 -
环的性质
映射形成一个或多个闭环。每个环表示一组元素,它们可以通过彼此交换排序完成。例如:- 一个大小为
k的环需要至少k - 1次交换才能完成排序。 - 对所有环的最小交换次数为: 最小交换次数=n−环的数量\text{最小交换次数} = n - \text{环的数量}
- 一个大小为
-
高效求解环数量
为了快速计算环的数量,可以使用并查集:- 初始化每个位置为独立集合。
- 如果两个位置之间可以通过合法交换连接,则将它们合并到同一个集合。
- 最终,每个独立集合对应一个环。
3. 算法实现
以下是实现细节:
-
检查奇偶性不匹配:
- 遍历数组,验证每个元素
a[i]的奇偶性是否与其目标位置i一致。 - 若发现不匹配,直接返回
-1。
- 遍历数组,验证每个元素
-
使用并查集求解环数量:
- 构建并查集,将可以交换的元素连接起来。
- 统计并查集中独立集合的数量,计算最小交换次数。
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
复杂度分析
- 奇偶性检查:
遍历数组检查奇偶性是否匹配。 - 并查集构建:
并查集的合并与查找操作复杂度为近似常数 (阿克曼函数的反函数)。 - 统计环数量:
遍历所有节点,统计独立集合数量。
总复杂度:,接近线性。
总结
本题通过将排序问题转化为图中的环分解问题,高效地计算了最少交换次数。关键在于通过并查集构建交换关系,并利用奇偶性验证确保可行性。最终算法实现了清晰的逻辑和较优的性能,适用于 ICPC 等竞赛场景。