剑指 Offer 51:数组中的逆序对 (Java分治思想)

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 剑指 Offer 51:数组中的逆序对 (Java分治思想),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、题目描述

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

示例 1:

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

限制:

0 <= 数组长度 <= 50000

二、思路讲解

        首先一眼看上去,二重循环暴力十分省事,然而时间复杂度为平方阶,超时。

public class Solution {

    public int reversePairs(int[] nums) {
        int count = 0
        for (int i = 0; i <nums.length-1; i++) {
            for (int j = i + 1; j < len; j++) {
                if (nums[i] > nums[j]) {
                    count++;
                }
            }
        }
        return count;
    }
}

 

        那么我们就思考更好的算法:

        求逆序对类型的题目里,经常使用归并排序,因为在高级排序算法(归并排序、快速排序)里,能够明显看到阶段性排序效果的算法就是归并排序。

        举个例子,例如在归并排序的最后一步:合并的过程中,有前后两部分(这两部分分别有序的):

        5        6        7        8    I    1        2        3        4

        ↑                                       ↑

        i                                         j

        根据合并的思路,j指针所指小于i指针所指,会将4首先放回拷贝数组中。因为左半边数组也是有序的,所以1小于左半边的所有数字,因此逆序对个数可以直接加4。详解见力扣官方视频,讲得很好:数组中的逆序对

三、Java代码实现

        其实只用在归并排序中加一行代码就行了。

        代码中有几个细节问题:

        int mid = left +(right – left) / 2; 避免left+right超过int范围;

        归并排序中,合并时,如果nums[x]<=nums[y]的等号不取,排序将不稳定

class Solution {

    int count = 0;

    public int reversePairs(int[] nums) {
        this.count = 0;
        mergeSort(nums, 0, nums.length-1, new int[nums.length]);
        return count;
    }

    //传一个用于拷贝的temp数组,比每次都创建新数组的效率要更高
    void mergeSort(int []nums, int i, int j, int []temp) {
        if(i >= j) {
            return;
        }
        int k = i + (j-i)/2;    //取中间值,这样的写法避免i+j超过整型范围
        mergeSort(nums, i, k, temp);
        mergeSort(nums, k+1, j, temp);
        merge(nums, i, k, j, temp);
    }

    void merge(int []nums, int i, int k, int j, int []temp) {
        int x = i;
        int y = k+1;
        int index = 0;
        while(x<=k && y<=j) {
            while(x<=k && nums[x]<=nums[y]) {   //如果这里nums[x]<=nums[y]不写等号,归并排序将不稳定
                temp[index++] = nums[x++];
            }
            while(y<=j && nums[y]<nums[x]) { //注意这里不能写等号,因为逆序对是不能相等的
                count += (k-x+1);   //比归并排序多的一行
                temp[index++] = nums[y++];
            }
        }
        while(x<=k) {
            temp[index++] = nums[x++];
        }
        while(y<=j) {
            temp[index++] = nums[y++];
        }
        for(int m=i; m<=j; m++) {
            nums[m] = temp[m-i];
        }
    }
}

四、时空复杂度分析

        时间复杂度:        O(NlogN)        归并排序的时间复杂度

        空间复杂度:        O(N)                归并排序的空间复杂度,因为需要一个拷贝数组

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/124970.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!