🔥题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
输入:[7, 3, 2, 6, 0, 1, 5, 4]
输出:17
☘️解析
告诉你一个秘密:【逆序对】与【归并排序】息息相关。
怎么会有联系呢?
不妨重新思考一下归并排序的核心思路:将数组左侧部分和右侧部分分别递归排序,然后进行二路归并。
此时的数组有一个重要的中间状态:左侧和右侧分别是一个递增数组。
这正是关键!看下面的例子,你会立即理解:
[ 2 4 6 8 ] [ 3 5 7 9 ] 无逆序对
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 计数逆序对(4,3)(6,3)(8,3)
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 无逆序对
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 计数逆序对(6,5)(8,5)
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 无逆序对
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 计数逆序对(8,7)
i j
[ 2 4 6 8 ] [ 3 5 7 9 ] 无逆序对
i j
每次在二路归并时,都顺便进行逆序对计数即可。
或许你还会有疑问,这样会不会重复计数?不会。因为每次计数的逆序对,大数字和小数字都分别来自于左侧数组和右侧数组;二路归并完毕,即计数完毕后,左右数组成为一个数组,即逆序对进入了同一数组,这个组合再也不会被计数第二次。
最后,可以看看K神提供的清晰图解(链接):
🧊代码
class Solution {
public int reversePairs(int[] nums) {
this.count = 0;
this.temp = new int[nums.length];
mergeSort(nums, 0, nums.length - 1);
return count;
}
private int count;
private int[] temp;
private void mergeSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private void merge(int[] arr, int left, int mid, int right) {
int i = left;
int j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
// 相比于归并排序模板,只多出这么一行 !!!
count += (mid - i + 1);
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
while (left <= right) {
arr[left] = temp[left];
left++;
}
}
}
🌸补充
【逆序对】与【归并排序】息息相关,你是否已经理解了呢?