题目:如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对总数。例如输入{7,5,6,4},一共有5个逆序对,分别是(7,6),(7,5),(7,4),(6,4),(5,4)。
思路:
思路1:暴力解决。顺序扫描数组,对于每个元素,与它后面的数字进行比较,因此这种思路的时间复杂度为o(n^2)。
思路2:
上述思路在进行比较后,并没有将相关信息留下,其实在比较之后可以进行局部的排序,从而降低比较的次数,降低时间复杂度。
可通过如下步骤求逆序对个数:先把数组逐步分隔成长度为1的子数组,统计出子数组内部的逆序对个数,然后再将相邻两个子数组合并成一个有序数组并统计数组之间的逆序对数目,直至合并成一个大的数组。其实,这是二路归并的步骤,只不过在归并的同事要多进行一步统计。因此时间复杂度o(nlogn),空间复杂度o(n),如果使用原地归并排序,可以将空间复杂度降为o(1)。
书中使用经典二路归并排序实现。以{7,5,6,4}为例,过程如下:
[7 5 6 4]
/ \ 分:将长度为4的数组分成长度为2的数组
[7 5] [6 4]
/ \ / \ 分:将长度为2的数组分成长度为1的数组
[7] [5] [6] [4]
\ / \ / 和:1->2,并记录子数组内的逆序对
[5 7] [4 6]
\ / 和:2->4,并记录子数组内的逆序对
[4 5 6 7]
根据归并排序,只修改几行代码的java版本如下:
package chapter5;
public class P249_InversePairs {
public static int sort(int[] arr){
if(arr==null||arr.length<2)
return 0;
return mergeSort(arr,0,arr.length-1);
}
public static int mergeSort(int[] arr,int l,int r){
if(l==r) return 0;
int mid=l+((r-l)>>1);//() is very important.
return mergeSort(arr,l,mid)+mergeSort(arr,mid+1,r)+merge(arr,l,mid,r);
}
public static int merge(int[] arr,int l,int m,int r){
int[] help=new int[r-l+1];
int i=0;
int res=0;
int p1=l;
int p2=m+1;
while (p1<=m&&p2<=r){
res+=arr[p1]>arr[p2]?(m-p1+1):0;//modify//小的一个马上要被取走,所以必须由大的那边所有数与小的对比
help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];//merge sort
}
while (p1<=m){
help[i++]=arr[p1++];
}
while (p2<=r){
help[i++]=arr[p2++];
}
for(i=0;i<help.length;i++){
arr[l+i]=help[i];//start at l !!!
}
return res;
}
public static void main(String[] args) {
int[] arr=new int[]{7,5,6,4};
System.out.println(sort(arr));
}
}
基于以上思路,java参考代码如下:
public class InversePairs {
public static int inversePairs(int[] data){
if(data==null || data.length<2)
return 0;
return mergeSortCore(data, 0, data.length - 1);
}
public static int mergeSortCore(int[] data,int start,int end){
if(start>=end)
return 0;
int mid = start+(end-start)/2;
int left = mergeSortCore(data,start,mid);
int right = mergeSortCore(data,mid+1,end);
int count = mergerSortMerge(data,start,mid,end);
return left+right+count;
}
//start~mid, mid+1~end
public static int mergerSortMerge(int[] data,int start,int mid,int end){
int[] temp = new int[end-start+1];
for(int i=0;i<=end-start;i++)
temp[i] = data[i+start];
int left = 0,right = mid+1-start,index = start,count = 0;
while (left<=mid-start && right<=end-start){
if(temp[left]<=temp[right])
data[index++] = temp[left++];
else{
data[index++] = temp[right++];
count += (mid-start)-left+1;
}
}
while (left<=mid-start)
data[index++] = temp[left++];
while (right<=end-start)
data[index++] = temp[right++];
return count;
}
public static void main(String[] args){
System.out.println(inversePairs(new int[]{7,5,6,4}));
System.out.println(inversePairs(new int[]{5,6,7,8,1,2,3,4}));
}
}
相关问题:小和问题。
在一个数组中, 每一个数左边比当前数小的数累加起来, 叫做这个数组的小和。 求一个数组
的小和。
例子:
[1,3,4,2,5]
1左边比1小的数, 没有;
3左边比3小的数, 1;
4左边比4小的数, 1、 3;
2左边比2小的数, 1;
5左边比5小的数, 1、 3、 4、 2;
所以小和为1+1+3+1+1+3+4+2=16
java参考代码如下:
package chapter5;
public class P249_SmallSum {
public static int smallsum(int[] arr){
if(arr==null||arr.length<2)
return 0;
return mergeSort(arr,0,arr.length-1);
}
public static int mergeSort(int[] arr,int l,int r){
if(l==r) return 0;
int mid=l+((r-l)>>1);//() is very important.
return mergeSort(arr,l,mid)+mergeSort(arr,mid+1,r)+merge(arr,l,mid,r);
}
public static int merge(int[] arr,int l,int m,int r){
int[] help=new int[r-l+1];
int i=0;
int res=0;
int p1=l;
int p2=m+1;
while (p1<=m&&p2<=r){
//res+=arr[p1]>arr[p2]?(m-p1+1):0;//modify
res+=arr[p1]<arr[p2]?(r-p2+1)*arr[p1]:0;
help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];//merge sort
}
while (p1<=m){
help[i++]=arr[p1++];
}
while (p2<=r){
help[i++]=arr[p2++];
}
for(i=0;i<help.length;i++){
arr[l+i]=help[i];//start at l !!!
}
return res;
}
public static void main(String[] args) {
int[] arr={7,5,6,4};
int[] arr1={1,3,4,2,5};
System.out.println(smallsum(arr));//5
System.out.println(smallsum(arr1));//16
}
}
测试用例:
a.功能测试(输入未经排序的数组、递增排序的数组、递减排序的数组;输入的数组中包含重复的数字)。
b.边界值测试(输入的数组中只有两个数字;输入的数组中只有一个数字)。
c.特殊输入测试(表示数组的指针为nullptr指针)。