[路飞][LeetCode]剑指 Offer 51. 数组中的逆序对

355 阅读1分钟

「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战

看一百遍美女,美女也不一定是你的。但你刷一百遍算法,知识就是你的了~~

谁能九层台,不用累土起!

题目地址

题目

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

示例 1:

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

限制:

0 <= 数组长度 <= 50000

解题思路

按照老套路,我们肯定会尝试一下暴力解。

var reversePairs = function(nums) {
    let count = 0
    for(let i = 0;i<nums.length;i++){
        for(let j = i+1;j<nums.length;j++){
            if(nums[j]<nums[i]) count++
        }
    }
    return count
};

但是提交后我们会发现超出了时间限制,也就是说明O(n²)的时间复杂度不能满足题目。

看来只能换一种思路了。

那我们接下来看看归并操作的妙用。

首先,先简单介绍一下归并操作:

归并操作的工作原理如下:

  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  • 重复步骤3直到某一指针超出序列尾
  • 将另一序列剩下的所有元素直接复制到合并序列尾.

因此,我们在归并的过程中只要计算每个小区间的逆序对数,进而计算出大区间的逆序对数。

解题代码

var reversePairs = function(nums) {

    let arr = new Array(nums.length) // 用数组暂存区间排序结果
    
    function getCount(left,right){
        if(left>=right) return 0 // 如果当前区间中元素数量小于等于1 返回0
        const mid = (left+right)>>1 // 获取中间索引
        let count = 0
        count += getCount(left,mid)  // 对左右两个区间进行递归获取逆序对数量
        count += getCount(mid+1,right)

        let index = left  
        let low = left
        let high = mid+1
        while(low<=mid||high<=right){
            if(high>right || (low<=mid && nums[low]<=nums[high])){
                arr[index++] = nums[low++]  // 左区间
            } else{
                arr[index++] = nums[high++]  // 元素落在右区间,左区间中未处理的元素都与当前元素组成逆序对
                count += mid-low+1
            }

        }

        
        for(let i = left;i<=right;i++){// 用排序后的数组更新原数组
            nums[i] = arr[i]
        }

        return count

    }
    return getCount(0,nums.length-1)
};

如有任何问题或建议,欢迎留言讨论!