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

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

一、题目描述



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


示例 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)                归并排序的空间复杂度,因为需要一个拷贝数组


相关文章
|
19小时前
|
存储 Java ice
【Java开发指南 | 第十六篇】Java数组及Arrays类
【Java开发指南 | 第十六篇】Java数组及Arrays类
8 3
|
2天前
|
存储 算法 搜索推荐
滚雪球学Java(27):从零开始学习数组:定义和初始化
【5月更文挑战第2天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
7 3
|
4天前
|
Java 索引
Java中数组详解
Java中数组详解
44 19
|
4天前
|
Java
解析java中的数组
解析java中的数组
11 3
|
5天前
|
存储 安全 Java
Java一分钟之-数组的创建与遍历
【5月更文挑战第8天】本文介绍了Java中数组的基本概念、创建与遍历方法,强调了类型匹配和数组越界问题。示例展示了如何创建整数数组并初始化元素,同时提供了避免数组越界的策略。对于遍历,文章提到了for循环和增强型for循环,并给出了防止错误的建议,如正确声明类型、初始化数组、安全索引操作及使用合适的数据结构。遵循这些指导可帮助开发者有效管理Java数组并减少错误。
17 0
|
14天前
|
存储 Java 索引
Java数组
Java数组
23 0
|
14天前
|
存储 算法 Java
【Java探索之旅】掌握数组操作,轻松应对编程挑战
【Java探索之旅】掌握数组操作,轻松应对编程挑战
12 0
|
14天前
|
存储 Java C语言
【Java探索之旅】基本类型与引用类型 数组的应用 二维数组
【Java探索之旅】基本类型与引用类型 数组的应用 二维数组
14 0
|
14天前
|
存储 机器学习/深度学习 Java
【Java探索之旅】数组使用 初探JVM内存布局
【Java探索之旅】数组使用 初探JVM内存布局
26 0
|
14天前
|
存储 Java 编译器
【Java探索之旅】数组概念与初始化指南:动静结合
【Java探索之旅】数组概念与初始化指南:动静结合
22 0