剑指Offer【35】--数组中的逆序对

代码已经同步到刷题仓库【CodeSolution】
仓库地址:https://github.com/Damaer/CodeSolution
文档地址:https://damaer.github.io/CodeSolution/

题目

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

示例 1:

输入: [7,5,6,4]
输出: 5

限制:
0 <= 数组长度 <= 50000

思路以及解答

首先,也就是数组中任意两个数,只要前面的数大于后面的数,就是逆序对。先来一次暴力破解:遍历任意两个数,只要符合条件,总数就增加1。

class Solution {
    public int reversePairs(int[] nums) {
    int i=0, j=0, sum=0;
    for( i=0; i<nums.length; i++ ){
        for( j=i+1; j<nums.length; j++ ){
            if( nums[j] < nums[i] ) sum++;
        }
    }
    return sum;
    }
}

毫无疑问,这个时间复杂度是O(n2),肯定会超时。

第二种方法就是利用分治的思想,在归并排序的基础上稍微改动即可。以数组[8,6,4,2,7,5,3,1]为例:

我们可以发现,其实在合并的过程中,两个有序的数组,可以直接计算出逆序数组的个数。我们以[8,6,4,2,7,5,3,1],实际上分为[8,6,4,2][7,5,3,1],逆序的个数为第一部分[8,6,4,2]中的逆序个数+第二部分[7,5,3,1]中的逆序个数,还有第三部分是[8,6,4,2]中的元素相对[7,5,3,1]的逆序个数。

分为两半之后的逆序个数,一看就是分治法,递归即可,而两部分的相对逆序,我们可以在合并有序数组的时候得出。

合并的时候使用双指针,i指向第一个数组的第一个元素,j指向第二个数组的第一个元素。哪一个元素小,就将该元素写入新的数组中,同时指针后移。

如果第二个数组中的元素小于第一个数组中的元素,那么就构成了逆序对,逆序对的个数:如果中间分隔是索引是mid,那么构成逆序对的个数为mid-i+1

代码如下:

public class Solution35 {
    public static void main(String[] args) {
        int[] nums = {8, 6, 4, 2, 7, 5, 3, 1};
        Solution35 solution35 = new Solution35();
        int result = solution35.InversePairs(nums);
        System.out.println(result);
    }

    public int InversePairs(int[] array) {
        if (array == null || array.length < 2) {
            return 0;
        }
        int[] nums = new int[array.length];
        return getNums(array, nums, 0, array.length - 1) % 1000000007;
    }

    public int getNums(int[] array, int[] nums, int left, int right) {
        if (left >= right) {
            return 0;
        }
        int mid = left + (right - left) / 2;
        int leftNum = getNums(array, nums, left, mid) % 1000000007;
        int rightNum = getNums(array, nums, mid + 1, right) % 1000000007;
        return leftNum + rightNum + mergeNum(array, nums, left, mid, right);
    }

    public int mergeNum(int[] array, int[] nums, int left, int mid, int right) {
        for (int i = left; i <= right; i++) {
            nums[i] = array[i];
        }
        int count = 0;
        int i = left, j = mid + 1;
        for (int k = left; k <= right; k++) {
            if (i == mid + 1) {
                array[k] = nums[j];
                j++;
            } else if (j == right + 1) {
                array[k] = nums[i];
                i++;
            } else if (nums[i] <= nums[j]) {
                array[k] = nums[i];
                i++;
            } else {
                array[k] = nums[j];
                j++;
                count = (count + (mid - i + 1)) % 1000000007;
            }
        }
        return count % 1000000007;
    }
}

时间复杂度同归并排序的时间复杂度,也就是O(nlogn),空间复杂度,由于临时需要使用一个数组,所以为O(n)

有一个很坑的地方:只要涉及到加和的地方都有可能溢出,一旦溢出就会导致结果出错,数据量大,很难调试。凡是涉及到加和的地方都要% 1000000007。真的很迷…

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。个人写作方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都完全正确,但是我保证所写的均经过实践或者查找资料。遗漏或者错误之处,还望指正。

2020年我写了什么?

开源编程笔记

平日时间宝贵,只能使用晚上以及周末时间学习写作,关注我,我们一起成长吧~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值