在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
输入: [7,5,6,4]
输出: 5
链接: leetcode.
解题思路:利用归并排序的思想。在归并排序中,需要通过两个指针比较前后两个元素的大小,当前元素大于后元素时,这就是一个有效的逆序对。同时,如果某一前元素大于后元素,则前段元素区间中,该元素的后面元素也会大于后段区间中该后元素,所以,也需要将这一部分逆序对计算上。
这样做会不会产生重复的逆序对计算呢?答案是不会的,每次比较的两个相等长度的段中的元素,随着递归,前面计算的段已经被合并,段的长度会增长,之后比较的元素一定不会再被重复比较,这样就不会产生重复计算。
下面提供了两种做法,还是推荐递归做法,这样没用考虑很多边界问题。
- 递归做法
class Solution {
int res = 0;
public int reversePairs(int[] nums) {
merge(nums, 0, nums.length - 1);
return res;
}
public void merge(int[] nums, int l, int r) {
if(l >= r) return;
int mid = l + r >> 1;
merge(nums, l, mid);
merge(nums, mid + 1, r);
int i = l, j = mid + 1;
List<Integer> temp = new ArrayList<>();
while(i <= mid && j <= r) {
if(nums[i] <= nums[j]) {
temp.add(nums[i]);
i++;
} else {
// 逆序对计算,数量应当是i及i到mid之间的元素数量
// 因为i大于j位置的元素,那i之后的元素也一定大于j位置的元素
res += mid - i + 1;
temp.add(nums[j]);
j++;
}
}
while(j <= r) {
temp.add(nums[j]);
j++;
}
while(i <= mid) {
temp.add(nums[i]);
i++;
}
i = l;
while(i <= r) {
nums[i] = temp.get(i - l);
i++;
}
}
}
- 非递归
class Solution {
public int reversePairs(int[] nums) {
int n = nums.length;
if(n == 0 || n == 1) return 0;
int res = 0;
for(int len = 1; len < n; len *= 2) {
for(int i = 0; i < n; i += 2 * len) {
List<Integer> temp = new ArrayList<>();
int l = i, r = i + len;
int mid = i + len;
while(l < mid && r < i + len * 2 && r < n) {
if(nums[l] > nums[r]) {
res += mid - l;
temp.add(nums[r]);
r++;
} else{
temp.add(nums[l]);
l++;
}
}
while(l < mid && l < n) {
temp.add(nums[l++]);
}
while(r < i + len * 2 && r < n) {
temp.add(nums[r++]);
}
for(int j = 0; j < temp.size(); j++) {
nums[j + i] = temp.get(j);
}
}
}
return res;
}
}