← 返回题目列表

如何用归并排序统计数组中的逆序对?

高频 中等 第 9 / 26 题 更新于 2026/08/03
排序归并排序逆序对

简化版

逆序对是满足 i < jnums[i] > nums[j] 的二元组。

可以用归并排序统计。递归统计左半部分和右半部分的逆序对,再在合并两个有序数组时统计跨左右两边的逆序对。

当左边当前元素大于右边当前元素时,左边从当前指针到末尾的所有元素都大于右边当前元素,因此一次可以加上这段数量。

详细版

归并排序统计逆序对的关键在合并阶段。

左右两边已经有序:

left:  2, 4, 6
right: 1, 3, 5

如果 left[i] > right[j],因为 left 已经有序,所以:

left[i], left[i+1], ..., left[end]

都大于 right[j]

于是逆序对数量增加:

mid - i + 1

整体复杂度是 O(n log n)

完整版教学

1. 什么是逆序对

逆序对表示数组中两个元素的相对顺序和升序要求相反。

定义是:

i < j 且 nums[i] > nums[j]

例如:

[3, 1, 2]

逆序对有 (3,1)(3,2),数量是 2

2. 暴力方法为什么慢

最直接的方法是枚举所有二元组:

for i in 0..n-1:
  for j in i+1..n-1:
    if nums[i] > nums[j]:
      count++

复杂度是 O(n^2)

n 很大时不可接受。

3. 归并排序为什么能统计

归并排序把数组分成左右两半。

逆序对可以分成三类:

类型位置
左半内部逆序对递归统计
右半内部逆序对递归统计
跨左右逆序对合并时统计

前两类交给递归,最关键的是第三类。

4. 合并阶段如何一次统计多个

左右两边已经分别有序。

如果:

left[i] > right[j]

那么由于 left[i..end] 都不小于 left[i],它们都大于 right[j]

所以可以一次增加:

left.length - i

而不是一个个数。

归并统计逆序对的效率来自“有序性让一次比较贡献一批答案”。

5. 伪代码怎么写

合并时可以这样写:

merge(left, right):
  i = 0, j = 0
  while i < left.length and j < right.length:
    if left[i] <= right[j]:
      output left[i]
      i++
    else:
      count += left.length - i
      output right[j]
      j++

注意相等时不算逆序对,所以用 <= 先取左边。

6. 为什么相等不算

逆序对要求:

nums[i] > nums[j]

如果两个值相等,不满足大于。

因此合并时遇到 left[i] <= right[j],应该先放左边,不增加计数。

这也让合并过程保持稳定。

7. 复杂度和溢出问题

归并排序层数是 log n,每层合并总共处理 n 个元素,所以时间是:

O(n log n)

逆序对数量最大可能是:

n * (n - 1) / 2

如果 n 很大,结果可能超过 int,应使用 long

8. 常见误区与追问

  • 误区:逆序对只能暴力枚举。 归并排序可以在 O(n log n) 内统计。
  • 误区:相等元素也算逆序对。 定义要求严格大于,相等不算。
  • 误区:合并后再统计跨区间。 跨区间统计要利用合并时左右有序的状态。
  • 追问:为什么能加 left.length - i 因为左半剩余元素都大于当前右半元素。
  • 追问:结果类型为什么可能要 long? 逆序对最大数量是二次级,可能超过 32 位整数。