持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
题目:数组中的逆序对指的是在一个数组中,前面的元素数值大于后面的元素,则构成逆序对,先要求求出给定数组的逆序对总数。
解题思路
按照常规模拟,数组中的前一个数大于后一个数则可构成逆序对,简单的想法是通过双层for循环来查看所有情况,当满足条件时则逆序对数量自增,可得代码如下:
public int reversePairs(int[] nums) {
int count = 0;
for(int i=0;i<nums.length-1;i++){
int value = nums[i];
for(int j=i+1;j<nums.length;j++){
if(nums[j]<value) count++;
}
}
return count;
}
这种方法的时间复杂度为,但此题为hard,显然不会这么简单,并且最终测试超时。再次观察数组,要达到比小的时间复杂度只可能有和,对于本题来说,显然是不可能的,那也只能是了,要达到此时间复杂度必然需要对数组进行二分,二分的思想目前了解的就是二分查找和归并排序了,甚至快速排序思想也和这个类似。但如何利用这个思想解答本题呢?
我们可以观察如下序列7, 6, 5, 4, 3, 2, 1,这是一个完全逆序的序列,此序列的逆序对总共为,计算公式即为,如果我们对这个序列进行排序,那么7在6前面,则7和6必然构成逆序对,如果6和7排序完成,而5小于6和7,则5进行排序必然会产生两个逆序对,依次类推,此思想是在序列进行排序时计算逆序对,排序的方式为归并排序,归并排序每次二分,直到每个数组元素个数都为1的时候停止划分,此时进行两两合并,如果左边数组元素大于右边则构成逆序对,并且逆序对的个数即为左边数组的元素个数(满足此结果的条件的左右两边数组都相对有序,归并排序刚好可以满足),通过归并排序可得最终代码如下:
public int reversePairs(int[] nums) {
if(nums.length<2) return 0;
return mergeSort(nums, 0, nums.length-1);
}
public int mergeSort(int[] nums, int left, int right){
if(left<right){
int mid = left + (right-left)/2;
int l = mergeSort(nums, left, mid);
int r = mergeSort(nums, mid+1, right);
int m = merge(nums, left, mid, right);
return l+r+m;
}else {
return 0;
}
}
public int merge(int[] nums, int left, int mid, int right){
int count = 0;
int[] res = new int[right - left + 1];
int l1 = left, l2 = mid+1, l3 = 0;
while(l1<=mid&&l2<=right){
if(nums[l1]<=nums[l2]){
res[l3++] = nums[l1++];
}else {
res[l3++] = nums[l2++];
count += (mid-l1+1);
}
}
while(l1<=mid){
res[l3++] = nums[l1++];
}
while(l2<=right){
res[l3++] = nums[l2++];
}
for(int i=0;i<res.length;i++){
nums[left+i] = res[i];
}
return count;
}