一、题目
牛客题目链接:数组中的逆序对_牛客题霸_牛客网
题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
数据范围: 对于 50% 的数据, size≤
对于 100% 的数据, size≤
数组中所有数字的值满足 0≤val≤
要求:空间复杂度 O(n) ,时间复杂度 O(nlogn)
输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:[1,2,3,4,5,6,7,0]
返回值:7
示例2
输入:[1,2,3]
返回值:0
二、解题思路
方案一:暴力两次循环
看到这个题,第一反应可能就是暴力两次循环,这种实现方案在思考上是最简单的,但是时间复杂度是o(n^2);不是期待的最优解。
方案二:归并统计法
这个方案的核心有两点 归并排序+合并统计。
归并排序:
先分:就是将数组分为两个子数组,两个子数组分为四个子数组,依次向下分,直到数组不能再分为止!
后并:就是从最小的数组按照顺序合并,从小到大或从大到小,依次向上合并,最后得到合并完的顺序数组!
合并统计:
在合并数组的时候,当发现右边的小于左边的时候,此时可以直接求出当前产生的逆序对的个数。
[逆序对]总数:1+2+4 = 7;
方法三:树状数组(扩展思路)
树状数组是如图所示的数组,它正好可以累加前缀和。
比如4统计123的前缀和,8统计1234567的前缀,而且因为是树状的,因此操作都是O(log2n)O(log2n)。
具体做法:
- step 1:首先利用一个辅助数组,复制原数组然后排序,得到一个有序序列。
- step 2:然后对原数组进行离散化操作,利用二分查找映射,将数字变成其在有序数组中的位置,即数组[1 5000 2 400 30] 映射为 ——> [1 5 2 4 3],这样我们能根据这个数字在有序数组中的位置和实际位置判断它前面有多少是逆序的,同时不必开辟数字原本大小的空间,减少空间需求。因为题目所给没有重复元素,因此也不用去重,还是nn个数字。
- step 3:然后从前往后遍历每个元素,查找在树状数组中的前缀和,表示这个元素在树状数组中出现过多少,前缀和做差,表示值在[array[i]+1,n][array[i]+1,n]中出现的次数,也就是逆序数个数。
三、代码实现:
java 归并统计法:
import java.util.*;
public class Solution {
int count = 0;
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int InversePairs (int[] nums) {
// write code here
//长度小于2 则无逆序对
int len = nums.length;
if (len < 2) {
return 0;
}
mergeSort(nums, 0, len - 1);
return count;
}
public void mergeSort(int[] nums, int left, int right) {
int mid = left + (right - left) / 2;
if (left < right) {
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
merge(nums, left, mid, right);
}
}
public void merge(int[] nums, int left, int mid, int right) {
//创建临时数组,长度为此时两个子数组加起来的长度
int[] temp = new int[right - left + 1];
int c = 0; //临时数组的下标起点
int s = left; //保存在原数组的起点下标值
int l = left; //左子数组的起始指针
int r = mid + 1; //右子数组的起始指针
while (l <= mid && r <= right) {
// 当左子数组的当前元素小的时候,跳过,无逆序对
if (nums[l] <= nums[r]) {
temp[c] = nums[l]; // 放入临时数组
c++; // 临时数组下标+1
l++; // 左子数组指针右移
} else { // 否则,此时存在逆序对
temp[c] = nums[r];
count += mid + 1 -l; // 逆序对的个数为 : 左子数组的终点- 当前左子数组的当前指针
count %= 1000000007;
c++; // 临时数组+1
r++; // 右子数组的指针右移
}
}
// 左子数组还有元素时,全部放入临时数组
while (l <= mid) {
temp[c++] = nums[l++];
}
// 右子数组还有元素时,全部放入临时数组
while (r <= right) {
temp[c++] = nums[r++];
}
// 将临时数组中的元素放入到原数组的指定位置
for (int num : temp) {
nums[s++] = num;
}
}
}
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;
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int InversePairs (int[] nums) {
// write code here
int n = nums.length;
int[] temp = new int[n];
System.arraycopy(nums, 0, temp, 0, n);
Arrays.sort(temp); //排序得到一份有序的数组
//二分法重新映射,将数字变成其在有序数组中的位置
for (int i = 0; i < n; ++i) {
//二分法查找在其在有序数组中的位置
nums[i] = Arrays.binarySearch(temp, nums[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(nums[i])) % mod;
bit.update(nums[i]);
}
return res;
}
}