35_InversePairs

 题目描述:
    在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
    输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

 思路:

    归并排序的改进。
    把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
    合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
    数组array[i]大于后面数组array[mid+1]~array[j],count += j-(mid+1)+1=j-mid
    易错点:测试用例输出结果比较大可能溢出,要对每次返回的count mod(1000000007)求余,否则结果出错

 

看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。
如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。
由于每个数字都要和O(n)这个数字比较,因此这个算法的时间复杂度为O(n^2)。
我们以数组{7,5,6,4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不拿ta和后面的每一个数字作比较,
否则时间复杂度就是O(n^2),因此我们可以考虑先比较两个相邻的数字。

 

(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
(c) 把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对;
    在上图(a)和(b)中,我们先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆成两个长度为1的子数组。
    接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7大于5,
    因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6}、{4}中也有逆序对(6,4)。
    由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组 排序 如上图(c)所示,
    以免在以后的统计过程中再重复统计。接下来我们统计两个长度为2的子数组子数组之间的逆序对。
    合并子数组并统计逆序对的过程如下图如下图所示。我们先用两个指针分别指向两个子数组的末尾,
    并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对,
    并且逆序对的数目等于第二个子数组中剩余数字的个数,如下图(a)和(c)所示。
    如果第一个数组的数字小于或等于第二个数组中的数字,则不构成逆序对,如图b所示。
    每一次比较的时候,我们都把较大的数字从后面往前复制到一个辅助数组中,确保 辅助数组
    (记为copy)中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位,
    接下来进行下一轮比较。



过程:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。参考代码如下:

 

package swordToOffer._35_InversePairs;

public class Solution {
    public int InversePairs(int[] array) {
        if (array == null || array.length == 0)
            return 0;
        int[] temp=new int[array.length];
        int total=MergeSortCount(array,0,array.length-1,temp);
        return total;
    }

    /*
    返回逆序对数
     */
    public int MergeSortCount(int[] arr, int low, int high, int[] temp) {
        //分到最小,设置递归结束条件,分治到最后是只有一个数,比如下标为1,此时就直接计算逻辑不要再左右递归了
        if (low == high || low > high) {
            return 0;
        }
        int mid = (low + high) / 2;
//        左递归和右递归
        int leftCount = MergeSortCount(arr, low, mid, temp);
        int rightCount = MergeSortCount(arr, mid + 1, high, temp);
        int count = 0;

        //处理逻辑
        int i = mid;//左数组的最后一位
        int j = high;//右数组的最后一位
        int t = high;//指向temp的high位
        while (i >= low && j >= (mid + 1)) {
            //两组有序数组中,选大的放入temp中,从最后开始,并计算count
            if (arr[i] > arr[j]) {
                //由于是升序,此时共有j-(mid+1)+1=j-mid个数比arr[i]小
                count = count + j - mid;//左边比右边大时才需要计算逆序对
                temp[t--] = arr[i--];
                if(count>=1000000007)//!!!重要数值过大求余,否则会溢出int范围,造成结果错误!!!
                {
                    count%=1000000007;
                }
            } else {
                temp[t--] = arr[j--];
            }

        }
        //其中一方放完,将剩下的一方都放入temp中
        while (i >= low) {
            temp[t--] = arr[i--];
        }
        while (j >= (mid + 1)) {
            temp[t--] = arr[j--];
        }
        //将temp复制到arr中
        for (int l = low; l <= high; l++) {
            arr[l] = temp[l];
        }
        //返回结果,逆序对个数是 左数组内部的逆序对+右数组内部逆序对+左右之间的逆序对
        return (leftCount + rightCount + count) % 1000000007;

    }
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值