题目的主要信息:
- 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
- 输入一个数组,求一个数组的全部逆序对,答案对1000000007取模
- 保证输入的数组中没有的相同的数字
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:归并排序(推荐使用)
知识点:分治
分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的子问题,子问题继续按照这样划分,直到问题可以被轻易解决;“治”指的是将子问题单独进行处理。经过分治后的子问题,需要将解进行合并才能得到原问题的解,因此整个分治过程经常用递归来实现。
思路:
因为我们在归并排序过程中会将数组划分成最小为1个元素的子数组,然后依次比较子数组的每个元素的大小,依次取出较小的一个合并成大的子数组。
//取中间
int mid = (left + right) / 2;
//左右划分合并
merge(divide(left, mid, data, temp), divide(mid + 1, right, data, temp));
这里我们也可以用相同的方法划分,划分之后相邻一个元素的子数组就可以根据大小统计逆序对,而不断往上合并的时候,因为已经排好序了,我们逆序对可以往上累计。我们主要有以下三个阶段。
具体做法:
- step 1: 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1.
- step 2: 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
- step 3: 合并阶段:将排好序的子序列合并,同时累加逆序对。
图示:
Java实现代码:
public class Solution {
public int mod = 1000000007;
public int mergeSort(int left, int right, int [] data, int [] temp){
//停止划分
if(left >= right)
return 0;
//取中间
int mid = (left + right) / 2;
//左右划分合并
int res = mergeSort(left, mid, data, temp) + mergeSort(mid + 1, right, data, temp);
//防止溢出
res %= mod;
int i = left, j = mid + 1;
for(int k = left; k <= right; k++)
temp[k] = data[k];
for(int k = left; k <= right; k++){
if(i == mid + 1)
data[k] = temp[j++];
else if(j == right + 1 || temp[i] <= temp[j])
data[k] = temp[i++];
//左边比右边大,答案增加
else{
data[k] = temp[j++];
// 统计逆序对
res += mid - i + 1;
}
}
return res % mod;
}
public int InversePairs(int [] array) {
int n = array.length;
int[] res = new int[n];
return mergeSort(0, n - 1, array, res);
}
}
C++实现代码:
class Solution {
public:
int mod = 1000000007;
int mergeSort(int left, int right, vector<int>& data, vector<int>& temp){
//停止划分
if(left >= right)
return 0;
//取中间
int mid = (left + right) / 2;
//左右划分合并
int res = mergeSort(left, mid, data, temp) + mergeSort(mid + 1, right, data, temp);
//防止溢出
res %= mod;
int i = left, j = mid + 1;
for(int k = left; k <= right; k++)
temp[k] = data[k];
for(int k = left; k <= right; k++){
if(i == mid + 1)
data[k] = temp[j++];
else if(j == right + 1 || temp[i] <= temp[j])
data[k] = temp[i++];
//左边比右边大,答案增加
else{
data[k] = temp[j++];
//统计逆序对
res += mid - i + 1;
}
}
return res % mod;
}
int InversePairs(vector<int> data) {
int n = data.size();
vector<int> res(n);
return mergeSort(0, n - 1, data, res);
}
};
Python实现代码
class Solution:
mod = 1000000007
def MergeSort(self, left: int, right: int, data: List[int], temp: List[int]) -> int:
# 停止划分
if left >= right:
return 0
# 取中间
mid = int((left + right) / 2)
# 左右划分合并
res = self.MergeSort(left, mid, data, temp) + self.MergeSort(mid + 1, right, data, temp)
# 防止溢出
res %= self.mod
i, j = left, mid + 1
for k in range(left, right+1):
temp[k] = data[k]
for k in range(left, right+1):
if i == mid + 1:
data[k] = temp[j]
j += 1
elif j == right + 1 or temp[i] <= temp[j]:
data[k] = temp[i]
i += 1
# 左边比右边大,答案增加
else:
data[k] = temp[j]
j += 1
# 统计逆序对
res += mid - i + 1
return res % self.mod
def InversePairs(self , data: List[int]) -> int:
n = len(data)
res = [0 for i in range(n)]
return self.MergeSort(0, n - 1, data, res)
复杂度分析:
- 时间复杂度:,归并排序利用分治思想,树型递归每次二分,总共能分为层,每层合并都需要遍历数组所有元素即
- 空间复杂度:,辅助数组temp长度为及递归栈最大深度不会超过
方法二:树状数组(扩展思路)
思路:
我们在统计逆序的时候,使用方法一的归并思想,就是利用排序好的部分直接获取逆序个数,而不是一个一个地比较,这样就像是把前面逆序对的个数累加起来,与前缀和类似。
而树状数组是如图所示的数组,它正好可以累加前缀和。
比如4统计123的前缀和,8统计1234567的前缀,而且因为是树状的,因此操作都是。
具体做法:
- step 1:首先利用一个辅助数组,复制原数组然后排序,得到一个有序序列。
- step 2:然后对原数组进行离散化操作,利用二分查找映射,将数字变成其在有序数组中的位置,即数组[1 5000 2 400 30] 映射为 ——> [1 5 2 4 3],这样我们能根据这个数字在有序数组中的位置和实际位置判断它前面有多少是逆序的,同时不必开辟数字原本大小的空间,减少空间需求。因为题目所给没有重复元素,因此也不用去重,还是个数字。
- step 3:然后从前往后遍历每个元素,查找在树状数组中的前缀和,表示这个元素在树状数组中出现过多少,前缀和做差,表示值在中出现的次数,也就是逆序数个数。
Java实现代码:
import java.util.*;
class BIT {
private int[] tree;
private int n;
//初始化树状数组的大小
public BIT(int m) {
this.n = m;
this.tree = new int[m + 1];
}
//使数组呈现2、4、8、16这种树状
public int lowbit(int x) {
return x & (-x);
}
//查询序列1到x的前缀和
public int query(int x) {
int res = 0;
while(x != 0){
res += tree[x];
x -= lowbit(x);
}
return res;
}
//序列x位置的数加1
public void update(int x) {
while(x <= n){
tree[x]++;
x += lowbit(x);
}
}
}
public class Solution {
public int mod = 1000000007;
public int InversePairs(int [] array) {
int n = array.length;
int[] temp = new int[n];
System.arraycopy(array, 0, temp, 0, n);
//排序得到一份有序的数组
Arrays.sort(temp);
//二分法重新映射,将数字变成其在有序数组中的位置
for (int i = 0; i < n; ++i)
//二分法查找在其在有序数组中的位置
array[i] = Arrays.binarySearch(temp, array[i]) + 1;
//建立大小为n的树状数组
BIT bit = new BIT(n);
int res = 0;
//统计逆序对
for(int i = 0; i < n; i++){
//前缀和做差
res = (res + bit.query(n) - bit.query(array[i])) % mod;
bit.update(array[i]);
}
return res;
}
}
C++实现代码:
class BIT {
private:
vector<int> tree;
int n;
public:
//初始化树状数组的大小
BIT(int m) : n(m), tree(m + 1) {}
//使数组呈现2、4、8、16这种树状
int lowbit(int x){
return x & (-x);
}
//查询序列1到x的前缀和
int query(int x){
int res = 0;
while(x){
res += tree[x];
x -= lowbit(x);
}
return res;
}
//序列x位置的数加1
void update(int x){
while(x <= n){
tree[x]++;
x += lowbit(x);
}
}
};
class Solution {
public:
int mod = 1000000007;
int InversePairs(vector<int> data) {
int n = data.size();
int res = 0;
vector<int> temp = data;
//排序得到一份有序的数组
sort(temp.begin(), temp.end());
//二分法重新映射,将数字变成其在有序数组中的位置
for(int i = 0; i < n; i++)
//二分法查找在其在有序数组中的位置
data[i] = lower_bound(temp.begin(), temp.end(), data[i]) - temp.begin() + 1;
//建立大小为n的树状数组
BIT bit(n);
//统计逆序对
for(int i = 0; i < n; i++){
//前缀和做差
res = (res + bit.query(n) - bit.query(data[i])) % mod;
bit.update(data[i]);
}
return res;
}
};
Python实现代码:
import copy
#二分查找函数
def binarySearch (arr, l, r, x):
if r >= l:
mid = int(l + (r - l) / 2)
if arr[mid] == x:
return mid
elif arr[mid] > x:
return binarySearch(arr, l, mid - 1, x)
else:
return binarySearch(arr, mid + 1, r, x)
else:
return -1
class BIT:
m = 0
tree = []
# 初始化
def __init__(self, m: int):
self.n = m
self.tree = [0 for i in range(m + 1)]
# 使数组呈现2、4、8、16这种树状
def lowbit(self, x: int) -> int:
return x & (-x)
# 查询序列1到x的前缀和
def query(self, x: int) -> int:
res = 0
while x:
res += self.tree[x]
x -= self.lowbit(x)
return res
#序列x位置的数加1
def update(self, x: int):
while x <= self.n:
self.tree[x] += 1
x += self.lowbit(x)
class Solution:
mod = 1000000007
def InversePairs(self , data: List[int]) -> int:
n = len(data)
temp = copy.deepcopy(data)
#排序得到一份有序的数组
temp.sort()
#二分法重新映射,将数字变成其在有序数组中的位置
for i in range(n):
#二分法查找在其在有序数组中的位置
data[i] = binarySearch(temp, 0, len(temp), data[i]) + 1
#建立大小为n的树状数组
bit = BIT(n)
res = 0
#统计逆序对
for i in range(n):
#前缀和做差
res = (res + bit.query(n) - bit.query(data[i])) % self.mod
bit.update(data[i])
return res
复杂度分析:
- 时间复杂度:,sort函数排序为,离散化过程中一共次二分法时间复杂度为,一共次,统计逆序对过程中,查询更新都是,一共次
- 空间复杂度:,辅助数组temp的长度和树状数组的长度都是