《剑指 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
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
(本节完)