【剑指Offer刷题系列】数组中的逆序对



问题描述

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 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 个交易逆序对。

原题链接: https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/


思路解析

本题要求在一个整数数组 record 中找到所有满足 i < jrecord[i] > record[j](i, j) 对的总数,即统计数组中的「交易逆序对」总数。由于数组的长度可能达到 50000,直接使用双重循环进行枚举会导致时间复杂度为 O(n²),在数据量较大时会超时。因此,我们需要采用更高效的算法。

方法一:归并排序法(分治算法)

核心思路

利用归并排序的分治思想,在排序的过程中统计逆序对的数量。具体来说,在合并两个已排序的子数组时,当左子数组中的元素大于右子数组中的元素时,说明左子数组中当前元素及其后面的所有元素都与右子数组中的当前元素构成逆序对。

详细步骤

  1. 分割数组

    • 将数组递归地分割成左右两个子数组,直到每个子数组只包含一个元素。
  2. 合并并统计逆序对

    • 在合并两个已排序的子数组的过程中,统计逆序对的数量。
    • 如果左子数组的当前元素 left[i] 大于右子数组的当前元素 right[j],则说明从 left[i]left[end] 的所有元素都与 right[j] 构成逆序对,数量为 len(left) - i
  3. 累加逆序对总数

    • 将每次合并过程中统计的逆序对数量累加,最终得到整个数组中的逆序对总数。

示例分析

以示例 1 为例:

record = [9,7,5,4,6]
  • 分割为 [9,7][5,4,6]
  • 继续分割 [9,7][9][7]
  • 合并 [9][7]
    • 比较 97,因为 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]
    • 比较 54,因为 5 > 4,形成一个逆序对 (5,4)
    • 比较 56,因为 5 < 6,不形成逆序对
    • 合并后的子数组为 [4,5,6]
  • 最后合并 [7,9][4,5,6]
    • 比较 74,因为 7 > 4,形成两个逆序对 (7,4)(9,4)
    • 比较 75,因为 7 > 5,形成两个逆序对 (7,5)(9,5)
    • 比较 76,因为 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),适用于大规模数据。

该方法在实际应用中广泛使用,尤其适用于需要统计逆序对或进行高效排序的场景。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值