经典算法问题:数组中的逆序对

《剑指 Offer》(第 2 版)第 51 题:数组中的逆序对

传送门:数组中的逆序对。

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

输入一个数组,求出这个数组中的逆序对的总数。

样例:

输入:[1,2,3,4,5,6,0]

输出:6

思路1:使用定义

挨个数出来,使用定义计算逆序数。不过时间复杂度是 。

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

这种思路虽然很直接,但编写出错的概率很低,在没有在线评测系统的时候,它可以作为一个“正确的”参考答案,用以检验我们自己编写的算法是否正确。

思路2:分治

这道题最经典的思路是使用分治法计算,借助“归并排序”的分治思想,排好序以后,逆序对就求出来了,时间复杂度为 。下面举例说明:例如:前有序数组:,后有序数组:。

做归并的时候,步骤如下:

第 1 步, 先出列, 比“后有序数组”中所有的元素都小,构成“顺序对”;

第 2 步, 出列, 比“后有序数组”中所有的元素都小,构成“顺序对”;

第 3 步, 出列,关键的地方在这里,“前有序数组”中所有剩下的元素 比 都大,构成 个 “逆序对”

第 4 步, 出列, 比“后有序数组”中所有剩下的元素都小,构成“顺序对”;

第 5 步, 出列,“前有序数组”中所有剩下的元素 比 都大,构成 个“逆序对”

第 6 步, 出列,“前有序数组”中所有剩下的元素 比 都大,构成 个“逆序对”

第 7 步, 出列, 比“后有序数组”中所有剩下的元素 都小,构成 个“顺序对”;

第 8 步, 出列,此时“前有序数组”为空。

因此,我们只需要在“前有序数组”非空,且“后有序数组”中有元素出列的时候,即上面的第 3、5、6 步计算“逆序对”就可以了。

Python 代码:

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

    def inversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        l = len(nums)
        if l < 2:
            return 0
        temp = [0 for _ in range(l)]
        return self.count_inversion_pairs(nums, 0, l - 1, temp)

    def count_inversion_pairs(self, nums, l, r, temp):
        """
        在数组 nums 的区间 [l,r] 统计逆序对
        :param nums:
        :param l: 待统计数组的左边界,可以取到
        :param r: 待统计数组的右边界,可以取到
        :param temp:
        :return:
        """
        # 极端情况下,就是只有 1 个元素的时候
        if l == r:
            return 0
        mid = l + (r - l) // 2
        left_pairs = self.count_inversion_pairs(nums, l, mid, temp)
        right_pairs = self.count_inversion_pairs(nums, mid + 1, r, temp)

        merge_pairs = 0
        # 代码走到这里的时候,
        # [l, mid] 已经完成了排序并且计算好逆序对
        # [mid + 1, r] 已经完成了排序并且计算好逆序对
        # 如果 nums[mid] <= nums[mid + 1],此时就不存在逆序对
        # 当 nums[mid] > nums[mid + 1] 的时候,就要继续计算逆序对
        if nums[mid] > nums[mid + 1]:
            # 在归并的过程中计算逆序对
            merge_pairs = self.merge_and_count(nums, l, mid, r, temp)
        # 走到这里有 nums[mid] <= nums[mid + 1] 成立,已经是顺序结构
        return left_pairs + right_pairs + merge_pairs

    def merge_and_count(self, nums, l, mid, r, temp):
        """
        前:[2,3,5,8],后:[4,6,7,12]
        我们只需要在后面数组元素出列的时候,数一数前面这个数组还剩下多少个数字,
        因为"前"数组和"后"数组都有序,
        因此,"前"数组剩下的元素个数 mid - i + 1 就是与"后"数组元素出列的这个元素构成的逆序对个数
         
        """
        for i in range(l, r + 1):
            temp[i] = nums[i]
        i = l
        j = mid + 1
        res = 0
        for k in range(l, r + 1):
            if i > mid:
                nums[k] = temp[j]
                j += 1
            elif j > r:
                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

说明:归并两个有序数组的时候,我们要借助额外的辅助空间,为此可以全局使用一个和原始数组等长的辅助数组,否则每一次进入 merge 函数都要 new 新数组,开销很大。

上述解法的缺点是修改了原始数组,排序完成以后,逆序数就计算出来了。为此:(1)我们可以引入一个索引数组;(2)或者直接拷贝一个原始数组,这样就不用修改原始数组了。

思路3:使用“树状数组”

关于树状数组,我写了一篇文章,在这里,体会什么是“离散化”。

class Solution(object):
    def inversePairs(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        class FenwickTree:
            def __init__(self, n):
                self.size = n
                self.tree = [0 for _ in range(n + 1)]

            def __lowbit(self, index):
                return index & (-index)

            # 单点更新:从下到上,最多到 size,可以取等
            def update(self, index, delta):
                while index <= self.size:
                    self.tree[index] += delta
                    index += self.__lowbit(index)

            # 区间查询:从上到下,最少到 1,可以取等
            def query(self, index):
                res = 0
                while index > 0:
                    res += self.tree[index]
                    index -= self.__lowbit(index)
                return res

        # 特判
        l = len(nums)
        if l < 2:
            return 0

        # 原始数组去除重复以后从小到大排序
        s = list(set(nums))

        # 构建最小堆,因为从小到大一个一个拿出来,用堆比较合适
        import heapq
        heapq.heapify(s)

        # 由数字查排名
        rank_map = dict()
        index = 1
        # 不重复数字的个数
        size = len(s)
        for _ in range(size):
            num = heapq.heappop(s)
            rank_map[num] = index
            index += 1

        res = 0
        # 树状数组只要不重复数字个数这么多空间就够了
        ft = FenwickTree(size)
        # 从后向前看,拿出一个数字来,就更新一下,然后向前查询比它小的个数
        for i in range(l - 1, -1, -1):
            rank = rank_map[nums[i]]
            ft.update(rank, 1)
            res += ft.query(rank - 1)
        return res

说明:中间将数字映射到排名是将原数组“离散化”,“离散化”的原因有 2 点:

1、树状数组我们看到,索引是从“”开始的,我们不能保证我们的数组所有的元素都大于等于 ;

2、即使元素都大于等于“”,为了节约树状数组的空间,我们将之“离散化”可以把原始的数都压缩到一个小区间。我说的有点不太清楚,这一点可以参考 树状数组 求逆序数 poj 2299。

LeetCode 第 315 题:计算右侧小于当前元素的个数

传送门:315. 计算右侧小于当前元素的个数。

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

解法1:自定义 BST 的解法,比较巧妙,参考:

https://segmentfault.com/a/1190000008233819

【算法】逆序对问题的四种解法(归并排序,BST,树状数组,线段树)及变形
https://blog.csdn.net/haolexiao/article/details/54989306

leetcode 315 Count of Smaller Numbers After Self 以及 BST总结。
https://segmentfault.com/a/1190000008233783

image-20190116233732944

Python 代码:

class Solution:

    def countSmaller(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """

        class TreeNode:
            def __init__(self, val):
                self.left = None
                self.right = None
                self.val = val
                self.left_node_sum = 0
                self.duplicate_times = 1

        def insert(node, val, index, cum_left_nodeSum, counter):
            if node is None:
                node = TreeNode(val)
                counter[index] = cum_left_nodeSum
                return node
            if node.val == val:
                node.duplicate_times += 1
                counter[index] = cum_left_nodeSum + node.left_node_sum
            elif node.val > val:
                node.left_node_sum += 1
                node.left = insert(node.left, val, index, cum_left_nodeSum, counter)
            else:
                node.right = insert(node.right, val, index,
                                    cum_left_nodeSum + node.duplicate_times + node.left_node_sum, counter)
            return node

        l = len(nums)
        if l == 0:
            return []
        if l == 1:
            return [0]

        # 从后向前填表
        res = [None for _ in range(l)]
        root = None
        for index in range(l - 1, -1, -1):
            root = insert(root, nums[index], index, 0, res)
        print(root)
        return res

树状数组 求逆序数 poj 2299
https://blog.csdn.net/u013445530/article/details/39829053

把离散化搞清楚

poj 2299 Ultra-QuickSort 求逆序数,树状数组解法,详细解析
https://blog.csdn.net/Lionel_D/article/details/43535741

白话数据结构
https://blog.csdn.net/column/details/acxxz.html

(本节完)

你可能感兴趣的:(经典算法问题:数组中的逆序对)