剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
思路
- 暴力解法,两个循环,依次比较,时间复杂度超了
- 归并排序:思想,先拆散,将待排序的数组从中间递归的拆分为两块,知道最小的块长度为1;再合并,合并左边,右边,两块有序对合并。
import org.junit.jupiter.api.Test;
public class Solution {
public int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) {//长度为0 or 1, 没有逆序对,直接返回0
return 0;
}
int[] copy = new int[len];//copy辅助数组,非原地操作
for (int i = 0; i < len; i++) {
copy[i] = nums[i];
}
int[] temp = new int[len];//temp排序用到辅助数组,每次排序都使用到同一个数组,只是用到的长度不一样,避免大量新建和销毁长短不一的排序数组的开销
return reversePairs(copy, 0, len - 1, temp);//对copy数组进行归并排序,范围从第一位到最后一位,辅助数组尾temp
}
private int reversePairs(int[] nums, int left, int right, int[] temp) {
if (left == right) {//左右相等,说明最后两块数组也合并了。
return 0;
}
int mid = left + (right - left) / 2;//避免溢出的写法
int leftPairs = reversePairs(nums, left, mid, temp);//统计左边逆序对
int rightPairs = reversePairs(nums, mid + 1, right, temp);//统计右边逆序对
if (nums[mid] <= nums[mid + 1]) {//nums[mid]为左边有序数组的最大的一位,mid+1为右边有序对最小的一块,最后一部合并不额外产生逆序对,直接返回二者和
return leftPairs + rightPairs;
}
int crossPairs = mergeAndCount(nums, left, mid, right, temp);//当左右两块均合并完成后,最后一部合并的时候的逆序对计数
return leftPairs + rightPairs + crossPairs; //总逆序对数
}
/**
* 最后合并两个有序数组的时候产生的逆序对数量
* @param nums 合并后的有序对
* @param left 左边边界
* @param mid
* @param right 右边界
* @param temp 辅助排序的数组
* @return 返回逆序对数量
*/
private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}
int i = left;//左有序第一位
int j = mid + 1;//右有序第一位
int count = 0;
for (int k = left; k <= right; k++) {
if (i == mid + 1) {
nums[k] = temp[j];
j++;
} else if (j == right + 1) {
nums[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
nums[k] = temp[i];
i++;
} else {
nums[k] = temp[j];
j++;
count += (mid - i + 1);
}
}
return count;
}
@Test
public void testCase(){
int[] arr = {7, 5, 6, 4};
System.out.println(reversePairs(arr));
}
}