[剑指offer] 数组中的逆序对

剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5

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

思路

  • 暴力解法,两个循环,依次比较,时间复杂度超了
  • 归并排序:思想,先拆散,将待排序的数组从中间递归的拆分为两块,知道最小的块长度为1;再合并,合并左边,右边,两块有序对合并。
import org.junit.jupiter.api.Test;

public class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;

        if (len < 2) {//长度为0 or 1, 没有逆序对,直接返回0
            return 0;
        }

        int[] copy = new int[len];//copy辅助数组,非原地操作
        for (int i = 0; i < len; i++) {
            copy[i] = nums[i];
        }

        int[] temp = new int[len];//temp排序用到辅助数组,每次排序都使用到同一个数组,只是用到的长度不一样,避免大量新建和销毁长短不一的排序数组的开销
        return reversePairs(copy, 0, len - 1, temp);//对copy数组进行归并排序,范围从第一位到最后一位,辅助数组尾temp
    }

    private int reversePairs(int[] nums, int left, int right, int[] temp) {
        if (left == right) {//左右相等,说明最后两块数组也合并了。
            return 0;
        }

        int mid = left + (right - left) / 2;//避免溢出的写法
        int leftPairs = reversePairs(nums, left, mid, temp);//统计左边逆序对
        int rightPairs = reversePairs(nums, mid + 1, right, temp);//统计右边逆序对

        if (nums[mid] <= nums[mid + 1]) {//nums[mid]为左边有序数组的最大的一位,mid+1为右边有序对最小的一块,最后一部合并不额外产生逆序对,直接返回二者和
            return leftPairs + rightPairs;
        }

        int crossPairs = mergeAndCount(nums, left, mid, right, temp);//当左右两块均合并完成后,最后一部合并的时候的逆序对计数
        return leftPairs + rightPairs + crossPairs; //总逆序对数
    }

    /**
     * 最后合并两个有序数组的时候产生的逆序对数量
     * @param nums 合并后的有序对
     * @param left 左边边界
     * @param mid
     * @param right 右边界
     * @param temp 辅助排序的数组
     * @return 返回逆序对数量
     */
    private int mergeAndCount(int[] nums, int left, int mid, int right, int[] temp) {
        for (int i = left; i <= right; i++) {
            temp[i] = nums[i];
        }

        int i = left;//左有序第一位
        int j = mid + 1;//右有序第一位

        int count = 0;
        for (int k = left; k <= right; k++) {

            if (i == mid + 1) {
                nums[k] = temp[j];
                j++;
            } else if (j == right + 1) {
                nums[k] = temp[i];
                i++;
            } else if (temp[i] <= temp[j]) {
                nums[k] = temp[i];
                i++;
            } else {
                nums[k] = temp[j];
                j++;
                count += (mid - i + 1);
            }
        }
        return count;
    }

    @Test
    public void testCase(){
        int[] arr = {7, 5, 6, 4};
        System.out.println(reversePairs(arr));
    }
}
日积月累,水滴石穿
原文地址:https://www.cnblogs.com/lonelyisland/p/14469048.html