剑指Offer对答如流系列 - 数组中的逆序对

面试题51:数组中的逆序对

题目描述

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

问题分析

大多数人的第一反应就是顺序扫描整个数组,对每个数字都和后面的数字比较大小,时间复杂度为O(n^2),效率太低。

利用归并排序的思想,先将数组分解成为n个长度为1的子数组,然后进行两两合并同时排好顺序。(在排序的时候计算逆序对)

归并排序是经典排序算法之一,其核心是将待排数组不断细分,然后排序最后再合并,这是经典的分治策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)

(a) 把长度为4的数组分解成两个长度为2的子数组;

(b) 把长度为2的数组分解成两个成都为1的子数组;

(c) 把长度为1的子数组 合并、排序并统计逆序对

(d) 把长度为2的子数组合并、排序并统计逆序对

在对两个子区域合并排序时,记左边区域(下标为start~mid)的引用为i,右边区域(下标为mid+1~end)的引用为j,两个引用都指向该区域内最大的数字,排序时:

  1. 如果i指向的数字大于j指向的数字,说明:逆序对有j-mid个,我们把i指向的数字放入临时创建的排序数组中,然后令i-1,指向该区域前一个数字,继续进行排序;
  2. 如果i指向的数字小于等于j指向的数字,说明暂时不存在逆序对,将j指向的数字放入临时创建的排序数组中,然后令j-1,指向该区域前一个数字,继续进行排序;
  3. 某一子区域数字都放入排序数组后,将另一个子区域剩下的数字放入排序数组中,完成排序;
  4. 最后将排序好的数字按顺序赋值给原始数组的两个子区域,以便合并后的区域与别的区域合并。

问题解决

    // 主程序
    public int inversePairs(int [] array) {
        if(array==null || array.length<=0) {
            return 0;
        }
        int count=getCount(array,0,array.length-1);
        return count;
    }

    private static int getCount(int[] array,int start,int end){
        if(start>=end) {
            return 0;
        }
        int mid=(end+start)>>1;
        int left=getCount(array,start,mid);
        int right=getCount(array,mid+1,end);

        // 合并
        int count=0;
        // 左边区域的引用
        int i=mid;
        // 右边区域的引用
        int j=end;
        // 临时区域
        int[] temp= new int[end-start+1];
        // 临时区域的引用
        int k=end-start;

        while(i>=start && j>=mid+1){
            if(array[i]>array[j]){
                count+=(j-mid);
                temp[k--]=array[i--];
            }else{
                temp[k--]=array[j--];
            }
        }
        while(i>=start) {
            temp[k--]=array[i--];
        }
        while(j>=mid+1) {
            temp[k--]=array[j--];
        }

        // temp已经排好序 拿过来即可
        for(k=0; k<temp.length; k++) {
            array[k+start]=temp[k];
        }

        return count+left+right;
    }

原文地址:https://www.cnblogs.com/JefferyChenXiao/p/12246542.html

时间: 2024-10-08 17:22:55

剑指Offer对答如流系列 - 数组中的逆序对的相关文章

剑指Offer对答如流系列 - 数组中数字出现的次数

面试题56:数组中数字出现的次数 题目描述 问题(1)数组中只出现一次的两个数字 一个整型数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度是O(n),空间复杂度是O(1). 问题(2)数组中唯一只出现一次的数字 在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次.请找出那个只出现一次的数字. 问题分析 问题(1)分析 在这篇文章剑指Offer对答如流系列 - 二进制中 1 的个数中,我们详细探讨了位运算,其中有重要的一条:两个相同的数异

剑指Offer对答如流系列 - 数组中出现次数超过一半的数字

面试题39:数组中出现次数超过一半的数字 题目描述 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2. 问题分析 大家最容易想到的思路是 数字次数超过一半,则说明排序之后数组中间的数字一定就是所求的数字. 既然是数组,要牵扯到排序,大家一般都会选用经典快速排序或者随机快速排序.随机快速排序由于每次划分的依据是从数组随机选出的,所以数据状况对它

剑指Offer对答如流系列 - 二进制中 1 的个数

面试题14:二进制中 1 的个数 题目描述 请实现一个函数,输入一个整数,输出该数二进制表示中1的个数.例如把9表示成二进制是1001,有2位是1.因此如果输入9,该函数输出2. 问题分析与解决 这道面试题归属于 <剑指Offer>位运算章节.遇到二进制相关的问题,很容易想到位运算,虽然种类不多(与.或.异或.左移.右移),但是搞起来是千变万化的.待会再和你侃一些骚操作,我们先看这道题. (一)思路一 "与运算"有一个性质:通过与对应位上为1,其余位为0的数进行与运算,可以

剑指Offer对答如流系列 - 数据流中的中位数

面试题41:数据流中的中位数 题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值. 所谓数据流,就是不会一次性读入所有数据,只能一个一个读取,每一步都要求能计算中位数. 问题分析 相信上一道题 最小的k个数 给了你容器的启示. 我们将读入的数据分为两部分,一部分数字小,另一部分大. 小的一部分采用大顶堆存放,大的一部分采用小顶堆存放.当总个数为偶数时,使

剑指Offer对答如流系列 - 圆圈中最后剩下的数字

面试题62:圆圈中最后剩下的数字 题目描述 0, 1, -, n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字. 例如,从数字0开始每次删除第3个数字,则删除的前四个数字是2 0 4 1 因此最后剩下的数字是3 问题分析 思路一: 既然涉及到数据的频繁删除,可以考虑使用链表来存放数据,每次对长度取余数可以实现循环操作. 思路二: 这种问题规律性非常强,其实已经有对这一规律背后的数学模型的探究,即约瑟夫环 举一个具体的场景: 据说著名犹太历

剑指Offer对答如流系列 - 链表中倒数第k个结点

面试题22:链表中倒数第k个结点 题目描述 输入一个链表,输出该链表中倒数第k个结点.为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点.例如一个链表有6个结点,从头结点开始它们的值依次是1.2.3.4.5.6.这个链表的倒数第3个结点是值为4的结点. 链表结点定义如下: public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } } 问题分析 链表

剑指Offer对答如流系列 - 求1+2+…+n

面试题64:求1+2+-+n 题目描述 求1+2+-+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). 问题分析 有了那么多限制,剩下的我们可以选择 单目运算符:++和--,双目运算符:+,-,移位运算符<>,关系运算符>,<等 逻辑运算符&&,||,&,|,^,赋值= 既然是一个等差数列,和为(n+1)*n/2 我们之前详细探讨了位运算剑指Offer对答如流系列 - 二进制中 1 的个

剑指Offer对答如流系列 - 不用加减乘除做加法

面试题65:不用加减乘除做加法 题目描述 写一个函数,求两个整数之和,要求在函数体内不得使用+.-.×.÷四则运算符号. 问题分析 我们之前详细探讨了位运算 剑指Offer对答如流系列 - 二进制中 1 的个数,已经非常非常详细了. 这道题仅仅是让做加法,我们除此之外还是做了乘除与减法. 记不清的朋友可以回头看看. 这里象征性地做一次解答吧 问题解答 public int add(int num1,int num2) { while(num2!=0){ int sum=num1^num2; in

剑指Offer对答如流系列 - 把数组排成最小的数

面试题45:把数组排成最小的数 题目描述 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个.例如输入数组{3, 32, 321},则打印出这3个数字能排成的最小数字321323. 问题分析 之前我们做过字符全排列的习题 剑指Offer对答如流系列 - 字符串的排列,但是将算法思想应用到这一题的话,效果不好,求出所有的组合,再计算出组合的最小值,这效率该多低啊. 我们还要进一步探究,看看有没有不错的规律,供我们使用. 因为数字拼接后的长度一样,拼接后的结果