1. 问题描述:
给定一个数组 nums ,如果 i < j 且 nums[i] > 2 * nums[j] 我们就将 (i,j) 称作一个重要翻转对。你需要返回给定数组中的重要翻转对的数量。
示例 1:
输入: [1,3,2,3,1]
输出: 2
示例 2:
输入: [2,4,3,5,1]
输出: 3
注意:
给定数组的长度不会超过50000。
输入数组中的所有数字都在32位整数的表示范围内。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-pairs/
2. 思路分析:
① 对于这种求解逆序对的题目,主要有两种解法,第一种解法使用树状数组来解决,第二种是使用的是归并排序的思路来解决。树状数组主要用来求解一个区间中需要单点更新并且需要求解区间和的问题,使用树状数组求解的经典问题有力扣307区域和检索,315计算右侧小于当前元素的个数,这些题目都是需要在区间中单点更新操作比较频繁并且需要求解区间和的题目,并且树状数组经常用来求解右边区间比自己大、小、或者是与自己有倍数关系的数字个数的题目。
② 由于这道题目的数据是在int范围之内的所以我们不能直接开一个这么大的数组或者列表,所以需要先对nums中的数字进行离散化。因为使用的是python语言我们可以先将nums中的所有数字以及它们的两倍加入到set集合中,这样可以用来去除重复出现的数字,将set集合转化为列表将所有数字从小到大排序,使用哈希表将这些数字映射到下标从1开始的连续位置,这样就可以节省一定的空间。对于这道题目来说我们可以查询以当前的nums[i]为左端点所有满足条件的条件的nums[j],我们其实可以逆序遍历nums这样会更好处理数据,因为求解的是满足nums[i] > 2 * nums[j] 的nums[j],也即求解的是满足nums[i] - 1 >= 2 * nums[j]的nums[j],查询对应的数目之后需要将当前数字2倍的位置上加上1,这里可以使用树状数组的add函数进行多个位置的动态更新,在树状数组某个位置上加上1表示当前位置的数目增加了1。
③ 第二种使用的是归并排序的思想,归并排序主要使用了分治的思想,每一次划分为两个区间,使得划分的区间通过递归之后分别是有序的,当递归两个区间结束之后那么归并两个递归之后的有序区间,其中需要使用一个辅助数组或者列表w来帮助我们将两个区间合并成一个有序的区间,对于这道题目来说我们可以在递归完两段区间之后使用双指针算法来找出当前这一段区间答案的数目(递归之后两段区间都是有序的所以可以使用双指针算法找到最终的答案),也即指针i指向左边区间一开始的位置,指针j指向右边区间一开始位置,在循环中找到当前第一个2 * nums[j]大于等于nums[i]对应的指针j的位置,当前 j - 1 - mid就是当前指针i对应的nums[i]满足nums[i] > 2 * nums[j]的数目(指针i往右走指针j肯定是往右走的所以可以使用双指针算法),对于每一段区间都是这样递归求解那么就可以求解出所有满足条件的数目。其中使用到了归并排序的模板,其实是在归并排序递归完左右区间之后多了求解答案的这一个步骤。
3. 代码如下:
树状数组:
class Solution:
n = 0
tr = None
def lowbit(self, x: int):
return x & -x
# 查询[1, x]的区间和
def query(self, x: int):
i = x
res = 0
while i > 0:
res += self.tr[i]
i -= self.lowbit(i)
return res
# 将x位置上加上v
def add(self, x: int, v: int):
i = x
n = self.n
while i <= n:
self.tr[i] += v
i += self.lowbit(i)
def reversePairs(self, nums) -> int:
t = nums + [2 * x for x in nums]
t = list(set(t))
# 哈希表用来离散化
dic = dict()
i = 1
t.sort()
for x in t:
dic[x] = i
i += 1
n = len(t)
self.n = n
self.tr = [0] * (n + 1)
res = 0
for i in range(len(nums) - 1, -1, -1):
# 减1求解的是小于等于的nums[i]的nums[j]的数目, 我们先要求解出当前数字在哈希表中的位置再查询[1:x]的满足条件的数目
res += self.query(dic[nums[i]] - 1)
self.add(dic[2 * nums[i]], 1)
return res
归并排序:
from typing import List
class Solution:
def mergeSort(self, nums: List[int], l: int, r: int):
if l >= r: return 0
mid = l + r >> 1
# 分治, 递归左右两个区间, 递归完之后两个区间都是有序的
res = self.mergeSort(nums, l, mid) + self.mergeSort(nums, mid + 1, r)
i, j = l, mid + 1
# 双指针算法用来计算答案
while i <= mid:
while j <= r and nums[i] > 2 * nums[j]: j += 1
res += j - 1 - mid
i += 1
# w为归并排序的辅助列表
w = list()
i, j = l, mid + 1
# 双指针算法将两个区间的的数字存储到辅助列表中
while i <= mid and j <= r:
if nums[i] <= nums[j]:
w.append(nums[i])
i += 1
else:
w.append(nums[j])
j += 1
# 左边的区间还有数字
while i <= mid:
w.append(nums[i])
i += 1
# 右边的区间还有数字
while j <= r:
w.append(nums[j])
j += 1
i = l
# 调整区间[l, r]是有序的
for j in range(len(w)):
nums[i] = w[j]
i += 1
return res
# 这里使用归并排序的思路, 每一次划分两段区间
def reversePairs(self, nums: List[int]) -> int:
return self.mergeSort(nums, 0, len(nums) - 1)