《剑指offer》面试题51:数组中的逆序对

题目:如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对总数。例如输入{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指针)。

参考:

https://www.jianshu.com/p/c7f98f5cc918

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值