剑指offer系列-面试题51. 数组中的逆序对(python)

1. 题目

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数

2. 解题思路

详情见 暴力解法、分治思想、树状数组

2.1 暴力法

这个没什么好说的

2.3 动态规划

f(n) = f(n-1) + m,前面的数组中比当前元素大的元素的个数。

2.3 分治思想(借助归并排序统计逆序数)

说明:理解这个算法需要对「归并排序」比较熟悉。掌握如果编写递归函数,每一次都一分为二拆分数组的子区间,然后在方法栈弹出的时候,一步一步合并两个有序数组,最后完成排序工作。

3. 代码实现

3.1 暴力法

O(n2),空间复杂度S(n),超时。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        size = len(nums)
        if size < 2:
            return 0
        res = 0
        for i in range(0, size - 1):
            for j in range(i + 1, size):
                if nums[i] > nums[j]:
                    res += 1
        return res

3.2 动态规划

O(n2),空间复杂度S(n),超时。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        """
        1. 动态规划
            状态转移方程为:
                f(i) = f(i-1) + m;m表示前面的数组中比nums[i]大的数字的个数
        """
        def count_large_number(arr, elem):
            count = 0
            for val in arr:
            	if val > elem:
            		count += 1
            return count
        
        if not nums: return 0
        dp, i = [0 for i in nums], 1

        while i < len(nums):
            m = count_large_number(nums, nums[i])
            dp[i] = dp[i-1] + m
            i += 1

        return dp[-1]

3.3 分治思想(借助归并排序统计逆序数)

class Solution:

    def reversePairs(self, nums: List[int]) -> int:
        size = len(nums)
        if size < 2:
            return 0

        # 用于归并的辅助数组
        temp = [0 for _ in range(size)]
        return self.count_reverse_pairs(nums, 0, size - 1, temp)

    def count_reverse_pairs(self, nums, left, right, temp):
        # 在数组 nums 的区间 [left, right] 统计逆序对
        if left == right:
            return 0
        mid = (left + right) >> 1
        left_pairs = self.count_reverse_pairs(nums, left, mid, temp)
        right_pairs = self.count_reverse_pairs(nums, mid + 1, right, temp)

        reverse_pairs = left_pairs + right_pairs
        # 代码走到这里的时候,[left, mid] 和 [mid + 1, right] 已经完成了排序并且计算好逆序对

        if nums[mid] <= nums[mid + 1]:
            # 此时不用计算横跨两个区间的逆序对,直接返回 reverse_pairs
            return reverse_pairs

        reverse_cross_pairs = self.merge_and_count(nums, left, mid, right, temp)
        return reverse_pairs + reverse_cross_pairs

    def merge_and_count(self, nums, left, mid, right, temp):
        """
        [left, mid] 有序,[mid + 1, right] 有序

        前:[2, 3, 5, 8],后:[4, 6, 7, 12]
        只在后面数组元素出列的时候,数一数前面这个数组还剩下多少个数字,
        由于"前"数组和"后"数组都有序,
        此时"前"数组剩下的元素个数 mid - i + 1 就是与"后"数组元素出列的这个元素构成的逆序对个数

        """
        for i in range(left, right + 1):
            temp[i] = nums[i]

        i = left
        j = mid + 1
        res = 0
        for k in range(left, right + 1):
            if i > mid:
                nums[k] = temp[j]
                j += 1
            elif j > right:
                nums[k] = temp[i]
                i += 1
            elif temp[i] <= temp[j]:
                # 此时前数组元素出列,不统计逆序对
                nums[k] = temp[i]
                i += 1
            else:
                # assert temp[i] > temp[j]
                # 此时后数组元素出列,统计逆序对,快就快在这里,一次可以统计出一个区间的个数的逆序对
                nums[k] = temp[j]
                j += 1
                # 例:[7, 8, 9][4, 6, 9],4 与 7 以及 7 后面所有的数都构成逆序对
                res += (mid - i + 1)
        return res

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/bao-li-jie-fa-fen-zhi-si-xiang-shu-zhuang-shu-zu-b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4. 总结

这道题我不会做。

5. 参考文献

[1] 剑指offer丛书
[2] 剑指Offer——名企面试官精讲典型编程题
[3] 暴力解法、分治思想、树状数组

猜你喜欢

转载自blog.csdn.net/besmarterbestronger/article/details/106480649