跳转至 leetcode 作答界面
在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。
示例 1:
输入:record = [9, 7, 5, 4, 6]
输出:8
解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。
限制:
0 <= record.length <= 50000
思考的角度可以转换成 => 对于数组中的每个元素,如何找出左侧比自己大的元素的数量,能否在归并排序的合并过程中做到这一点呢?
概述
在归并排序的左部分与右部分的合并过程中,累加逆序对的统计结果
为什么会使用到归并排序?
直观来看,使用暴力统计法即可,即遍历数组的所有数字对并统计逆序对数量。此方法时间复杂度为
O(N²)
,观察题目给定的数组长度范围:0 < len(record) < 50000
,可知此复杂度不可接受
「归并排序」与「逆序对」是息息相关的,主要有以下两个原因:
以上两点为统计逆序对提供了充足的可能
复习归并排序(分治思想)
归并排序使用分治的思想,先分成左子集和右子集分别排序,将排完后的左子集和右子集合并。 合并过程中需开辟一个额外空间为
O(n)
的临时变量
归并排序代码(归并排序类似二叉树后序遍历的思想)
代码框架
def mergesort(nums: List[int], lo: int, hi: int) -> None:
if lo == hi:
return
mid = (lo + hi) // 2
# 利用定义,排序 nums[lo..mid]
mergesort(nums, lo, mid)
# 利用定义,排序 nums[mid+1..hi]
mergesort(nums, mid + 1, hi)
# 后序位置,左子数组和右子数组已经被排好序
# 合并两个有序数组,使 nums[lo..hi] 有序
merge(nums, lo, mid, hi) # merge() 中需要利用 tmp 临时数组
完整代码
class Solution:
def __init__(self):
self.tmp = []
def sortArray(self, nums):
self.tmp = [0] * len(nums)
self.mergeSort(nums, 0, len(nums)-1)
return nums
def mergeSort(self, nums, lo, hi):
if lo == hi :
return
mid = (lo + hi) // 2
# 后序遍历框架
self.mergeSort(nums, lo, mid)
self.mergeSort(nums, mid+1, hi)
self.tmp[lo:hi+1] = nums[lo: hi+1]
i, j, k = lo , mid + 1, lo
while k <= hi:
while i <= mid and j <= hi:
if self.tmp[i] <= self.tmp[j]:
nums[k] = self.tmp[i]
i += 1
else:
nums[k] = self.tmp[j]
j += 1
k += 1
# 左子数组走完了
if i > mid:
nums[k:hi+1] = self.tmp[j:hi+1]
k = hi + 1
else:
nums[k:hi+1] = self.tmp[i:mid+1]
k = hi + 1
本题关键:如何通过归并排序找出每个元素左侧比自己大的元素的数量
举个例子来找规律…
假设归并排序来到要合并「左子数组(已排好序):[2,3,6,7]」和「右子数组(已排好序):[0,1,4,5]」的阶段,如下图所示…
当左子数组与右子数组开始合并时,指针 i
,j
分别指向两者的起始点,如上图所示,此时我们发现, nums[j](0)
是小于 nums[i](2)
的,又由于左子数组和右子数组都已经是升序排列好的,所以左子数组中 nums[i](2)
右侧的所有元素肯定都是大于nums[j](0)
的,此时此刻,对于 nums[j](0)
这个元素,就找到了 4 个逆序对,分别为 「2-0」「3-0」「6-0」「7-0」,然后将这个 4 加给结果变量,然后继续归并排序的常规流程…
于是我们发现规律:
归并排序在合并两个有序的数组时,指针 i
,j
分别在左子数组和右子数组上游走,当遇到「左子数组当前元素 > 右子数组当前元素」 时,就意味着 「左子数组从当前元素至末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」,而这若干 「逆序对」的个数也很容易算出:Δ = mid - i + 1
(mid 是左子数组最右侧元素的索引)
其实对照归并排序的代码,会发现就多了两行统计 res 的值
class Solution:
def __init__(self):
self.tmp = []
def reversePairs(self, record: List[int]) -> int:
self.tmp = [0] * len(record)
return self.merge_sort(record, 0 , len(record)-1)
# 归并排序, 返回当前数组的逆序对
def merge_sort(self, record, lo, hi):
mid = (lo + hi) //2
if lo >= hi:
return 0
res = self.merge_sort(record, lo, mid) + self.merge_sort(record, mid+1, hi)
# 将 record 移动到 tmp 数组
self.tmp[lo: hi+1] = record[lo: hi+1]
i,j,k = lo, mid + 1,lo
while k <= hi:
while i <= mid and j <= hi:
if self.tmp[i] > self.tmp[j]:
record[k] = self.tmp[j]
res += mid - i + 1
j += 1
else:
record[k] = self.tmp[i]
i += 1
k += 1
# i 走完了
if i > mid:
record[k:hi+1] = self.tmp[j:hi+1]
k = hi + 1
else:
record[k:hi+1] = self.tmp[i:mid+1]
k = hi + 1
return res
递归算法的复杂度:子问题个数 × 解决一个子问题的复杂度
对归并排序而言,时间复杂度集中在mergeSort(..)
的执行次数,可以将归并排序联想成二叉树后序遍历的场景
树高:logN
每一层子问题累加的复杂度:O(N)
【 N 为原数组的长度 N】
总复杂度:O(NlogN)
归并排序是一种稳定的排序算法,相同的元素排序后相对位置不变