求使数组非降序的lexicographical swap次数:高效算法探索
字典序交换的高效计数算法问题
定义
字典序交换(lexicographical swap):每一步选择最早的逆序对(i,j)——即先找最左侧的i(存在j>i使得a[i]>a[j]),再在i之后找索引最小的j满足a[i]>a[j]——进行交换,重复操作直到数组有序。
示例
数组[3,2,1]的交换过程:
- 交换索引(0,1) →
[2,3,1] - 交换索引(0,2) →
[1,3,2] - 交换索引(1,2) →
[1,2,3]
共需3次交换。
当前暴力模拟解法的最坏时间复杂度为O(n²),初始数组可为任意顺序。
技术问题
是否存在时间复杂度优于O(n²)的高效算法,无需显式模拟每一步交换即可计算总交换次数?
已有尝试
该问题和O(n log n)时间的逆序数统计类似,但字典序约束改变了交换顺序;曾尝试适配Fenwick树(BIT)或线段树,但未能正确建模该约束。
暴力模拟代码实现
def lexicographical_swaps(arr): n = len(arr) swaps = 0 while not is_sorted(arr): # 寻找最早的逆序对 for i in range(n-1): found = False for j in range(i+1, n): if arr[i] > arr[j]: # 找到最早的(i,j)逆序对,执行交换 arr[i], arr[j] = arr[j], arr[i] swaps += 1 found = True break if found: break return swaps
高效解法思路
实际上,这种字典序交换过程的总交换次数等于数组的逆序数,因此可以直接用O(n log n)时间的逆序数统计算法来计算,无需模拟每一步交换。
关键推导
该交换过程中,每次选择的是最左侧的i,以及i之后第一个比a[i]小的j(索引最小的j满足a[i]>a[j])。由于j是i之后第一个小于a[i]的元素,因此对于所有i<k<j,都有a[k]≥a[i]>a[j]。交换i和j后:
- 原逆序对(i,j)被消除
- 对于i<k<j,原逆序对(k,j)(共j-i-1个)会被替换为新的逆序对(i,k)(同样j-i-1个),这部分逆序对数量不变
- 其他逆序对不受影响
因此每一次交换都会让数组的逆序数恰好减少1。当数组有序时逆序数为0,总交换次数等于初始数组的逆序数。
O(n log n)实现方案
可以使用**Fenwick树(树状数组)**来统计逆序数,步骤如下:
- 对数组进行离散化处理(处理重复元素和大数情况)
- 从数组末尾向前遍历,对于每个元素x:
- 查询Fenwick树中已插入的、小于x的元素数量,累加到逆序数中
- 将x插入Fenwick树中
- 最终累加的结果即为总交换次数
代码示例(Python)
class FenwickTree: def __init__(self, size): self.n = size self.tree = [0]*(self.n + 1) def update(self, idx, delta=1): while idx <= self.n: self.tree[idx] += delta idx += idx & -idx def query(self, idx): res = 0 while idx > 0: res += self.tree[idx] idx -= idx & -idx return res def count_lex_swaps(arr): # 离散化处理 sorted_unique = sorted(set(arr)) rank = {v:i+1 for i, v in enumerate(sorted_unique)} # 树状数组索引从1开始 max_rank = len(sorted_unique) ft = FenwickTree(max_rank) inversions = 0 for num in reversed(arr): r = rank[num] # 查询当前已插入的、比num小的元素数量 inversions += ft.query(r - 1) ft.update(r) return inversions # 测试用例 print(count_lex_swaps([3,2,1])) # 输出3,与示例一致 print(count_lex_swaps([3,4,2,1])) # 输出5,符合推导结果
内容的提问来源于stack exchange,提问作者ISG
相关产品推荐
相关产品推荐

