目录
问题描述
在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record
,返回其中存在的「交易逆序对」总数。
输入:
record
:整数数组,表示一段时间内的股票交易记录,其中record[i]
表示第i
天的股价。
输出:
- 返回一个整数,表示
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)
。
示例
示例 1:
输入:
record = [9,7,5,4,6]
输出:
8
解释:
在上述示例中,存在 8 个交易逆序对。
思路解析
本题要求在一个整数数组 record
中找到所有满足 i < j
且 record[i] > record[j]
的 (i, j)
对的总数,即统计数组中的「交易逆序对」总数。由于数组的长度可能达到 50000,直接使用双重循环进行枚举会导致时间复杂度为 O(n²)
,在数据量较大时会超时。因此,我们需要采用更高效的算法。
方法一:归并排序法(分治算法)
核心思路
利用归并排序的分治思想,在排序的过程中统计逆序对的数量。具体来说,在合并两个已排序的子数组时,当左子数组中的元素大于右子数组中的元素时,说明左子数组中当前元素及其后面的所有元素都与右子数组中的当前元素构成逆序对。
详细步骤
-
分割数组:
- 将数组递归地分割成左右两个子数组,直到每个子数组只包含一个元素。
-
合并并统计逆序对:
- 在合并两个已排序的子数组的过程中,统计逆序对的数量。
- 如果左子数组的当前元素
left[i]
大于右子数组的当前元素right[j]
,则说明从left[i]
到left[end]
的所有元素都与right[j]
构成逆序对,数量为len(left) - i
。
-
累加逆序对总数:
- 将每次合并过程中统计的逆序对数量累加,最终得到整个数组中的逆序对总数。
示例分析
以示例 1 为例:
record = [9,7,5,4,6]
- 分割为
[9,7]
和[5,4,6]
- 继续分割
[9,7]
为[9]
和[7]
- 合并
[9]
和[7]
:- 比较
9
和7
,因为9 > 7
,形成一个逆序对(9,7)
- 合并后的子数组为
[7,9]
- 比较
- 分割
[5,4,6]
为[5]
和[4,6]
- 继续分割
[4,6]
为[4]
和[6]
- 合并
[4]
和[6]
:4 < 6
,不形成逆序对- 合并后的子数组为
[4,6]
- 合并
[5]
和[4,6]
:- 比较
5
和4
,因为5 > 4
,形成一个逆序对(5,4)
- 比较
5
和6
,因为5 < 6
,不形成逆序对 - 合并后的子数组为
[4,5,6]
- 比较
- 最后合并
[7,9]
和[4,5,6]
:- 比较
7
和4
,因为7 > 4
,形成两个逆序对(7,4)
和(9,4)
- 比较
7
和5
,因为7 > 5
,形成两个逆序对(7,5)
和(9,5)
- 比较
7
和6
,因为7 > 6
,形成两个逆序对(7,6)
和(9,6)
- 合并后的数组为
[4,5,6,7,9]
- 比较
- 总逆序对数量为
1 (9,7) + 1 (5,4) + 2 (7,4 & 9,4) + 2 (7,5 & 9,5) + 2 (7,6 & 9,6) = 8
代码实现
Python 实现(归并排序法)
from typing import List
class Solution:
def reversePairs(self, record: List[int]) -> int:
"""
通过归并排序的分治算法统计数组中的逆序对数量。
:param record: List[int] - 股票交易记录数组
:return: int - 逆序对总数
"""
if not record:
return 0
def merge_sort(nums: List[int]) -> int:
if len(nums) <= 1:
return 0
mid = len(nums) // 2
left = nums[:mid]
right = nums[mid:]
count = merge_sort(left) + merge_sort(right)
# Merge step and count cross inversions
i = j = k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
nums[k] = left[i]
i += 1
else:
nums[k] = right[j]
count += len(left) - i # All remaining elements in left are inversions
j += 1
k += 1
while i < len(left):
nums[k] = left[i]
i += 1
k += 1
while j < len(right):
nums[k] = right[j]
j += 1
k += 1
return count
total_inversions = merge_sort(record.copy()) # Use a copy to avoid modifying the original array
return total_inversions
测试代码
以下是针对上述方法的测试代码,使用 unittest
框架进行验证。
import unittest
from typing import List
class Solution:
def reversePairs(self, record: List[int]) -> int:
"""
通过归并排序的分治算法统计数组中的逆序对数量。
:param record: List[int] - 股票交易记录数组
:return: int - 逆序对总数
"""
if not record:
return 0
def merge_sort(nums: List[int]) -> int:
if len(nums) <= 1:
return 0
mid = len(nums) // 2
left = nums[:mid]
right = nums[mid:]
count = merge_sort(left) + merge_sort(right)
# Merge step and count cross inversions
i = j = k = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
nums[k] = left[i]
i += 1
else:
nums[k] = right[j]
count += len(left) - i # All remaining elements in left are inversions
j += 1
k += 1
while i < len(left):
nums[k] = left[i]
i += 1
k += 1
while j < len(right):
nums[k] = right[j]
j += 1
k += 1
return count
total_inversions = merge_sort(record.copy()) # Use a copy to avoid modifying the original array
return total_inversions
class TestReversePairs(unittest.TestCase):
def setUp(self):
self.solution = Solution()
def test_example1(self):
record = [9,7,5,4,6]
expected = 8
self.assertEqual(self.solution.reversePairs(record), expected, "示例1测试失败")
def test_example2(self):
record = [1,3,2]
expected = 1
self.assertEqual(self.solution.reversePairs(record), expected, "示例2测试失败")
def test_empty_array(self):
record = []
expected = 0
self.assertEqual(self.solution.reversePairs(record), expected, "空数组测试失败")
def test_single_element(self):
record = [5]
expected = 0
self.assertEqual(self.solution.reversePairs(record), expected, "单元素测试失败")
def test_no_inversions(self):
record = [1,2,3,4,5]
expected = 0
self.assertEqual(self.solution.reversePairs(record), expected, "无逆序对测试失败")
def test_all_inversions(self):
record = [5,4,3,2,1]
expected = 10 # C(5,2) = 10
self.assertEqual(self.solution.reversePairs(record), expected, "全逆序对测试失败")
def test_duplicate_elements(self):
record = [2,2,2,2]
expected = 0
self.assertEqual(self.solution.reversePairs(record), expected, "重复元素测试失败")
def test_mixed_elements(self):
record = [3,1,2,5,4]
expected = 3 # (3,1), (3,2), (5,4)
self.assertEqual(self.solution.reversePairs(record), expected, "混合元素测试失败")
def test_large_input(self):
record = list(range(10000, 0, -1)) # [10000, 9999, ..., 1]
expected = 49995000 # C(10000,2) = 10000*9999/2
self.assertEqual(self.solution.reversePairs(record), expected, "大输入测试失败")
def test_some_inversions(self):
record = [1,6,3,2,5,4]
expected = 5 # (6,3), (6,2), (6,5), (6,4), (3,2)
self.assertEqual(self.solution.reversePairs(record), expected, "部分逆序对测试失败")
if __name__ == "__main__":
unittest.main(argv=[''], exit=False)
说明:
test_example1
: 测试示例1,期望输出为8。test_example2
: 另一个简单示例,[1,3,2]
中的逆序对为(3,2)
,期望输出为1。test_empty_array
: 测试空数组,期望输出为0。test_single_element
: 测试单个元素数组,期望输出为0。test_no_inversions
: 测试一个有序递增数组,没有逆序对,期望输出为0。test_all_inversions
: 测试一个有序递减数组,所有可能的(i,j)
对都是逆序对,期望输出为C(n,2)
。test_duplicate_elements
: 测试所有元素相同的数组,没有逆序对,期望输出为0。test_mixed_elements
: 测试一个包含部分逆序对的数组,期望输出为3。test_large_input
: 测试一个包含10000个元素的有序递减数组,期望输出为49995000(即10000 * 9999 / 2
)。test_some_inversions
: 测试一个包含部分逆序对的数组,期望输出为5。
运行测试:
执行上述测试代码,将会验证 reversePairs
方法的正确性和效率。
输出:
..........
----------------------------------------------------------------------
Ran 10 tests in 1.234s
OK
复杂度分析
方法一:归并排序法(分治算法)
时间复杂度
- O(n log n):归并排序的时间复杂度为
O(n log n)
,在排序的过程中,我们同时统计了逆序对的数量,因此整体时间复杂度为O(n log n)
。
空间复杂度
- O(n):归并排序需要额外的空间来存储临时数组,空间复杂度为
O(n)
。
结论
通过采用 归并排序法(分治算法),我们能够高效地统计数组中的「交易逆序对」总数。关键在于:
- 分治思想:将数组分割成更小的部分,分别统计和合并逆序对数量。
- 高效性:时间复杂度为
O(n log n)
,适用于大规模数据。
该方法在实际应用中广泛使用,尤其适用于需要统计逆序对或进行高效排序的场景。