一、需求
- 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
- 输入一个数组,求出这个数组中的逆序对的总数。
二、暴力法
2.1 思路分析
- 使用双重循环,外循环遍历数组每个元素,内循环遍历外循环当前元素之后的元素,符合条件的进行计数;
2.2 代码实现
class Solution {
public int reversePairs(int[] nums) {
int count = 0;
for(int i = 0; i < nums.length; i++) {
for(int j = i+1; j < nums.length; j++) {
if(nums[i] > nums[j]) count++;
}
}
return count;
}
}
2.3 复杂度分析
- 时间复杂度为O(N^2);
- 空间复杂度为O(1);
三、动态规划法
3.1 思路分析
- 看到要求方案总数,故想到动态规划,定义状态dp[i]表示以nums[i]为结尾的逆序总数;
- 初始化状态dp[0] = 0;
- 关于状态转移方程,需要dp[i-1]加上[nums[0],nums[i-1]]中大于nums[i]的个数,假设用count表示,那么dp[i] = dp[i-1] + count;
- 该方法同暴力法一样,具有较高的时间复杂度,在测试中超时。
3.2 代码实现
class Solution {
public int reversePairs(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = 0;
for(int i = 1; i < nums.length; i++) {
int count = reverseCount(nums,i);
dp[i] = dp[i-1] + count;
}
return dp[nums.length-1];
}
//返回count
public int reverseCount(int[] nums,int i) {
int res = 0;
for(int j = 0; j < i; j++) {
res += nums[j] > nums[i] ? 1 : 0;
}
return res;
}
}
3.3 复杂度分析
- 时间复杂度为O(N^2);
- 空间复杂度为O(N),当然因为dp[i]只与dp[i-1]及常数count有关,因此可用变量tmp代替迭代过程,使得空间复杂度为O(1);
四、归并排序+分治法
4.1 思路分析
- 假设现在数组划分成的左(leftArr)、右(rightArr)数组均有序,通过leetcode官方题解可知,若遍历右数组中的元素(索引 j )小于遍历左数组的元素(索引 i )时,那么从索引 i ~ 索引 j 构成的逆序个数就是左数组中剩余的元素( i 之前的元素被合并到一个新的数组中);
- 我们首先在之前写的归并排序代码的基础上实现,然后在利用官方的形式实现一遍,虽然形式不同,但原理相同;
4.2 代码实现1
class Solution {
/**
* 该方法更具一般性,不用传入中间值mid
* @param arr
* @param left
* @param right
*/
public static int merge(int[] arr, int left, int right) {
//获取中间值
int mid = left + (right - left) / 2;
// 初始化左、右子数组长度
int leftSize = mid - left + 1;
int rightSize = right - mid;
// 新建左、右子数组
int[] leftArr = new int[leftSize];
int[] rightArr = new int[rightSize];
// 初始化左、右子数组
for (int i = left; i <= mid; i++) {
leftArr[i - left] = arr[i];
}
for (int j = mid + 1; j <= right; j++) {
rightArr[j - mid - 1] = arr[j];
}
// 新建三指针
int i = 0;
int j = 0;
int k = left;
int count = 0;
// 开始合并有序数组
while (i < leftSize && j < rightSize) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i];
i++;
k++;
} else {
arr[k] = rightArr[j];
j++;
k++;
count += mid - i - left + 1;
}
}
while (i < leftSize) {
arr[k] = leftArr[i];
i++;
k++;
}
while (j < rightSize) {
arr[k] = rightArr[j];
j++;
k++;
}
return count;
}
/**
* 该方法用来实现对无序数组的排序
* 假定左子数组区间[left,mid],右子数组区间[mid+1,right]
* @param arr
* @param left
* @param right
*/
public static int mergeSort(int[] arr,int left,int right) {
if(left == right) return 0;
int mid = left + (right - left)/2;
int leftCount = mergeSort(arr,left,mid);
int rightCount = mergeSort(arr,mid+1,right);
int crossCount = merge(arr,left,right);
return leftCount + rightCount + crossCount;
}
public static void main(String[] args) {
int[] arr = {6,8,9,10,1,5,2,7};
//int[] arr = {7,5,6,4};
int count = mergeSort(arr,0,7);
System.out.println(count);
}
}
4.3 代码实现2
class Solution {
public int reversePairs(int[] nums) {
if(nums.length < 2) {
return 0;
}
//拷贝原数组
int[] copy = new int[nums.length];
for(int i = 0; i < copy.length; i++) {
copy[i] = nums[i];
}
//用于归并排序
int[] temp = new int[nums.length];
return mergeSortPairs(copy,0,copy.length-1,temp);//[Left,Right]
}
/**
* 该方法用来返回逆序总数
*/
public int mergeSortPairs(int[] copy,int left,int right,int[] temp) {
if(left == right) {
return 0;
}
int mid = left + (right - left)/2;
int leftPairs = mergeSortPairs(copy,left,mid,temp);
int rightPairs = mergeSortPairs(copy,mid+1,right,temp);
//若当前已经是个有序数组了,就直接返回
if(copy[mid] <= copy[mid+1]) {
return leftPairs + rightPairs;
}
int crossPairs = mergeSortCount(copy,left,right,temp);
return leftPairs + rightPairs + crossPairs;
}
/**
* 该方法返回当前数组对应的逆序数
*/
public int mergeSortCount(int[] copy,int left,int right,int[] temp) {
for(int i = left; i <= right; i++) {
temp[i] = copy[i];
}
int mid = left + (right - left)/2;
int i = left;
int j = mid + 1;
int count = 0;
for(int k = left; k <= right; k++) {
if(i == mid + 1) {
copy[k] = temp[j];
j++;
} else if(j == right + 1) {
copy[k] = temp[i];
i++;
} else if(temp[i] <= temp[j]) {
copy[k] = temp[i];
i++;
} else {
copy[k] = temp[j];
j++;
count += mid - i + 1;
}
}
return count;
}
}