一,相关定义
1.什么是逆序数?
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
例如:在序列 { 2, 4, 3, 1 } 中,逆序依次为 (2,1),(4,3),(4,1),(3,1),因此该序列的逆序数为 4。
2.什么是逆序对?
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。
例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。
一个排列的逆序对的总数就是该排列的逆序数。(本质上逆序数和逆序对都是一样的)
二,问题描述
给定一个数组,求该数组中包含多少个逆序对。
三,求解方法
1.方法一:最原始的方法,利用两重循环进行枚举。该算法的时间复杂度为O()。
import java.util.Scanner;
public class mod2{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int []arr= new int[30];
System.out.print("请输入数组元素个数:");
int number=sc.nextInt();
System.out.print("请输入数组元素:");
for(int i=0;i<number;i++) {
arr[i]=sc.nextInt();
}
int count=0;
for(int i=0;i<number;i++){
for(int j=i+1;j<number;j++){
if(arr[i]>arr[j]){
count+=1;
}
}
}
System.out.println("逆序对的数目为:"+count);
}
}
2.方法二:利用归并排序的思想求解逆序对的个数,这是解决该问题的一种较为高效的算法。该算法的时间复杂度为O(nlogn)。
具体解法可以参考一下b站LeetCode的视频,讲的非常详细。
《剑指 Offer》 51. 数组中的逆序对【LeetCode 力扣官方题解】
就是在归并排序合并的同时进行计数这么个道理。
3.方法三:利用树状数组求逆序对,开始先将序列离散化以提高算法的时空效率,然后从序列的最左端开始建立树状数组,每创建一个就执行一次 i - getsum( x ) (x 为离散化的 值) 累加到ans上。最后的 ans即为所求的答案。注意在进行排序的时候如果两个数组的值相等, 则需要确保排序后原先的相对顺序不发生改变, 即确保使用稳定的排序方式。
四,方法二代码及调试
第一次测试
public class mod3{
public int reversePairs(int[] arr){
if(arr.length<2){
return 0;
}//判断数组中是否有元素,或者仅有一个元素,不存在有序对
int[] temp=new int[arr.length];//建立一个临时变量数组
return reversePairs(arr,0,arr.length-1,temp);//开始进行递归
}
public int reversePairs(int[] arr,int left,int right,int[] temp){
if(left==right){
return arr[left];
}//递归出口
int mid=left+(right-left)/2;
int leftPairs=reversePairs(arr,left,mid,temp);//左半部分的逆序对个数
int rightPairs=reversePairs(arr,mid+1,right,temp);//右半部分的逆序对个数
int crossPairs=mergePairs(arr,left,mid,right,temp);//跨中点的逆序对个数
return leftPairs+rightPairs+crossPairs;//逆序对个数总和
}
public int mergePairs(int[] arr,int left,int mid,int right,int[] temp){
int i=left,j=mid+1,k=left,count=0;
while(i<=mid&&j<=right){
if(arr[i]>=arr[j]){
temp[k]=arr[j];
j++;k++;
count+=mid-i+1;
}else{
temp[k]=arr[i];
i++;k++;
}
}
if(i==mid+1){
for(k=k-1;k<=right;k++){
temp[k]=arr[j];
}
}
if(j==right+1){
for(k=k-1;k<=right;k++){
temp[k]=arr[i];
}
}
return count;
}
public static void main(String[] args){
mod3 solution=new mod3();
int[] arr={3,1,4,5,2};
int finall=solution.reversePairs(arr);
System.out.println("该数组逆序对的个数为 :"+finall);
}
}
输出结果为17,原因不明。
第二次测试
修改了temp数组和arr数组判定和赋值位置,我发现如果不修改,就没法实现递归下让序列变得有序,但是输出结果仍为17,后经检查得知错误出在递归出口处返回的值是数组元素值,而不是0。
应该让每次出口处返回0,而完成递归后返回三个逆序对个数总和。
public class mod3{
public int reversePairs(int[] arr){
if(arr.length<2){
return 0;
}//判断数组中是否有元素,或者仅有一个元素,不存在有序对
int[] copy=arr.clone();
int[] temp=new int[arr.length];//建立一个临时变量数组
return reversePairs(copy,0,arr.length-1,temp);//开始进行递归
}
public int reversePairs(int[] arr,int left,int right,int[] temp){
if(left==right){
return arr[left];
}//递归出口
int mid=left+(right-left)/2;
int leftPairs=reversePairs(arr,left,mid,temp);//左半部分的逆序对个数
int rightPairs=reversePairs(arr,mid+1,right,temp);//右半部分的逆序对个数
int crossPairs=mergePairs(arr,left,mid,right,temp);//跨中点的逆序对个数
return leftPairs+rightPairs+crossPairs;//逆序对个数总和
}
public int mergePairs(int[] arr,int left,int mid,int right,int[] temp){
int[] tempArr=arr.clone();
int i=left,j=mid+1,k=left,count=0;
while(i<=mid&&j<=right){
if(tempArr[i]>=tempArr[j]){
arr[k]=tempArr[j];
j++;k++;
count+=mid-i+1;
}else{
arr[k]=tempArr[i];
i++;k++;
}
}
if(i==mid+1){
for(k=k-1;k<=right;k++){
arr[k]=tempArr[j];
}
}
if(j==right+1){
for(k=k-1;k<=right;k++){
arr[k]=tempArr[i];
}
}
return count;
}
public static void main(String[] args){
mod3 solution=new mod3();
int[] arr={3,1,4,5,2};
int finall=solution.reversePairs(arr);
System.out.println("该数组逆序对的个数为 :"+finall);
}
}
第三次测试
临时数组tempArr在对排序后剩余的左右数组中的元素进行归位的时候下标出现了问题,少了一位导致覆盖掉了一个元素。
if(i==mid+1){
for(; k<=right; k++){
arr[k]=tempArr[j];
}
}
if(j==right+1){
for(;k<=right;k++){
arr[k]=tempArr[i];
}
}
这里将k=k-1删掉就正常了。
最终完整代码
public class mod3{
public int reversePairs(int[] arr){
if(arr.length<2){
return 0;
}//判断数组中是否有元素,或者仅有一个元素,不存在有序对
int[] copy=arr.clone();
return reversePairs(copy,0,arr.length-1);//开始进行递归
}
public int reversePairs(int[] arr,int left,int right){
if(left==right){
return 0;
}//递归出口
int mid=left+(right-left)/2;
int leftPairs=reversePairs(arr,left,mid);//左半部分的逆序对个数
int rightPairs=reversePairs(arr,mid+1,right);//右半部分的逆序对个数
int crossPairs=mergePairs(arr,left,mid,right);//跨中点的逆序对个数
return leftPairs+rightPairs+crossPairs;//逆序对个数总和
}
public int mergePairs(int[] arr,int left,int mid,int right){
int[] tempArr=arr.clone();
int i=left,j=mid+1,k=left,count=0;
while(i<=mid&&j<=right){
if(tempArr[i]>=tempArr[j]){
arr[k]=tempArr[j];
j++;k++;
count+=mid-i+1;
}else{
arr[k]=tempArr[i];
i++;k++;
}
}
if(i==mid+1){
for(; k<=right; k++){
arr[k]=tempArr[j];
}
}
if(j==right+1){
for(;k<=right;k++){
arr[k]=tempArr[i];
}
}
return count;
}
public static void main(String[] args){
mod3 solution=new mod3();
int[] arr={3,2,9,1};
int finall=solution.reversePairs(arr);
System.out.println("该数组逆序对的个数为 :"+finall);
}
}
结果