数组中的逆序对

知识点 数组

描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P mod 1000000007
数据范围:  对于 50\%50% 的数据, size\leq 10^4size≤104
对于 100\%100% 的数据, size\leq 10^5size≤105

数组中所有数字的值满足 0 \le val \le 10000000≤val≤1000000

要求:空间复杂度 O(n)O(n),时间复杂度 O(nlogn)O(nlogn)

输入描述:

题目保证输入的数组中没有的相同的数字

         其实这道题目感觉蛮难的,刚开始完全没有思路。后面感觉这道题本质上应该是排序。数组中存在逆序,那假如我通过排序算法,在排序过程中每交换一次大的数到小的数后面,说明这两个数就是一个逆序,这样在排序过程中统计需要交换的次数就是逆序对数量。

        下面是我尝试写的代码,通过率5/6,有一个超时了。也不知道对不对。

//逆序对的总数=数组中每个位置处数字的逆序对的和
//算法复杂度O(nlogn),那么应该是一个for循环嵌套一个二分排序
//感觉这本质是一个排序算法,把这个数组按照某种排序算法排成正序,每两两交换一次数据说明有一个逆序对??
//使用了这种排序算法,通过率是5/6, 并提示您的程序未能在规定时间内运行结束,请检查是否循环有错或算法复杂度过大。
int length = array.length;
int deOrderNum = 0; //统计逆序对数量
for (int i = 1; i < length; i++) {
    for (int j = i; j>0 && (array[j-1] - array[j]) > 0 ; j--) {
        int temp = array[j];
        array[j] = array[j - 1];
        array[j - 1] = temp;
        deOrderNum = (deOrderNum + 1) % mod;
    }
}
return deOrderNum % 1000000007;

        看了大神的写法,用的是归并排序做的,看来自己方向是对的,这道题本质上是排序。

需要去学习一下归并排序的算法。这里我在看了算法演进图后尝试写一个。算法演进图如下

 可以看到,算法的思路就是“先拆分,再有序合并”

1、把大的数组平均拆分成两个子数组,并且子数组再递归地往下拆分,直到拆到最后子数组长度为1;

2、把数组长度为1的数组和相邻的数组长度为1的子数组组合成一个有序的父数组,然后递归地组合成更大的一个数组,直到数组长度等于原始数组长度。

代码实现(没写出来,糗大了)

        写不出来就去看了网上归并排序的写法,发现其实也没那么难,但是自己写的时候感觉困难重重,哎,自己就是垃圾。

        直接上网上的写法吧(参考文章“Java基础——递归实现归并排序和快速排序”)

算法描述

  • 把长度为n的输入序列分成两个长度为n/2的子序列;

  • 对这两个子序列分别采用归并排序;

  • 将两个排序好的子序列合并成一个最终的排序序列。

递归理解

        对整个数组进行排序 可以拆分成 对数组的左子数组排序,再对数组的右子数组排序,然后把左右子数组串成一个数组并排好序。假如用函数f(array)表示满足我们排序要求的函数,则

f(array)=merge(f(array[0~mid])+ f(array[mid + 1]~lenght - 1))

因为对左右子数组还需要再合并,所以假设有这么一个函数merge().

代码实现

/**
 * 分解数组
 * @param array
 * @param left
 * @param right
 */
public static void mergeSort(int array[],int left,int right){
    if(left>=right){
        return;
    }
    int mid = (left+right)>>>1;
    mergeSort(array,left,mid);
    mergeSort(array,mid+1,right);
    merge(array,left,mid,right);//合并
}


/**
 * 合并数组
 * @param array
 * @param left
 * @param mid
 * @param right
 */
public static void merge(int[] array,int left,int mid,int right){
    int s1 = left;
    int s2 = mid+1;
    int [] res = new int[right-left+1];
    int i=0;
    while(s1 <= mid && right >=s2){
        if(array[s1] <= array[s2]){
            res[i++] = array[s1++];
        }else {
            res[i++] = array[s2++];
        }
        //res[i++]= array[s1] <= array[s2] ? array[s1++] :array[s2++];
    }
    while(s1 <= mid){
        res[i++] = array[s1++];
    }
    while(s2 <= right){
        res[i++] = array[s2++];
    }
    System.arraycopy(res,0,array,0+left,res.length);
}

1、mergeSort递归函数首先要设置递归结束条件,并且该条件必须在函数最上面if(left>=right);

2、自己当时在尝试写递归实现的时候,在思考mergeSort应该返回什么,参数应该怎么设置?其实数组参数是传址的,所以不需要函数返回一个数组,只需要保证调用函数后array中元素是有序的就行;尝试过mergeSort只有一个表示数组的参数,但发现这样很不方便,我要获取数组的左或右子数组的话,我还需要用一个临时数组把左右子数组存起来。

3、归并递归不是像二分查找那样的尾递归了,函数中执行调用自己的函数后还有逻辑要执行。

大神写法参考文章https://github.com/Damaer/CodeSolution/blob/main/%E5%89%91%E6%8C%87Offer/%E5%89%91%E6%8C%87Offer35-%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E9%80%86%E5%BA%8F%E5%AF%B9.md

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值