题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5限制:
0 <= 数组长度 <= 50000
思路1 暴力求解
对数组中的每一个数Xi,它的后面有ni个比它小的数,则有多少个以数Xi开头的逆序对。
所以,总对数为:
其中N为数组长度。
得到如下代码:
class Solution:
def reversePairs(self, nums: List[int]) -> int:
N = len(nums)
ans = []
for i in range(N):
num = 0
for j in range(i+1, N):
if nums[i] > nums[j]:
num = num + 1
ans.append(num)
return sum(ans)
超出时间限制(时间复杂度O(N^2))
思路2 分治算法 + 归并排序
原理
分治思想即分而治之。
归并排序的含义如下:
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并操作,也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1};
第二次归并后:{6,100,202,301},{1,8,38};
第三次归并后:{1,6,8,38,100,202,301};
这就是归并排序
归并排序使用的是分治算法的思想,它和逆序对的关系如下图所示:
而[2,3,5,8,1,3,2,1]的逆序对的确为16.
具体地,以[2,3,5,8]和[1,1,2,3]为例,
使用双指针进行归并排序时:
- step1:lp指向2, rp指向1, 1<2, 故将rp指向的1添加到结果list中,res = [1], rp右移1;
- step2: lp指向2, rp指向1, 1<2, 故将rp指向的1添加到结果list中,res = [1, 1], rp右移1;
- step3: lp指向2, rp指向2, 2 不小于 2, 故将lp指向的2添加到结果list中,res = [1, 1, 2], lp右移1; 一旦lp右移,就记录下该lp指向的值对逆序对的贡献。它的贡献就是移动lp时rp移动的次数。例如,此时因为rp移动了两次,故2对逆序对的贡献为2;
- step4: lp指向3, rp指向2, 2 < 3, 故将rp指向的2添加到结果list中,res = [1, 1, 2, 2], rp右移1;
- step5: lp指向3, rp指向3, 3 不小于 3, 故将lp指向的3添加到结果list中,res = [1, 1, 2, 2, 3], lp右移1;3对逆序对的贡献为3;
- step6: lp指向5, rp指向3, 3 小于 5, 故将rp指向的3添加到结果list中,res = [1, 1, 2, 2, 3, 3], rp右移1;
- step7: 因为rp已经是最后一个,故左侧剩余的5和8可直接加入到res中, res = [1, 1, 2, 2, 3, 3, 5, 8]. 5和8对逆序对的贡献均为4.
因此,归并[2,3,5,8]和[1,1,2,3]的过程中,逆序对共有2+3+4+4=13组。
以此类推,就可以得到上图中的结果,也能够理解为什么可以通过归并排序来计算逆序对。
代码
使用递归法实现上述思想,此处使用递归的基本原理如下图:
代码如下
class Solution:
def reversePairs(self, nums: List[int]) -> int:
n = len(nums)
temp = [0] * n # 最终为归并排序好的list
count = merge(nums, temp, 0, n-1)
return count
def merge(nums, temp, l, r):
if l>=r:
return 0
mid = (l + r) // 2
count = merge(nums, temp, l, mid) + merge(nums, temp, mid+1, r)
i = l # 左指针
j = mid + 1 # 右指针
pos = l # 此次递归temp的初始位置
while i <= mid and j <= r:
if nums[i] <= nums[j]:
temp[pos] = nums[i]
i += 1
count += (j - (mid + 1))
else:
temp[pos] = nums[j]
j += 1
pos += 1
# 多余的部分
for k in range(i, mid + 1):
temp[pos] = nums[k]
count += (j - (mid + 1))
pos += 1
for k in range(j, r + 1):
temp[pos] = nums[k]
pos += 1
nums[l:r+1] = temp[l:r+1]
return count
复杂度
时间复杂度O(NlogN)
空间复杂度O(N)