Day35:数组中的逆序对

时间:2022-07-24
本文章向大家介绍Day35:数组中的逆序对,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

剑指Offer_编程题——数组中的逆序对

题目描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:

题目保证输入的数组中没有的相同的数字   数据范围:      对于%50的数据,size<=10^4      对于%75的数据,size<=10^5      对于%100的数据,size<=2*10^5

示例

输入:   1, 2, 3, 4, 5, 6, 7, 0 输出:   7

具体要求:

时间限制: C/C++ 2秒,其他语言4秒 空间限制: C/C++32M,其他语言64M

具体思路:

1、背景知识介绍   该题会用到数据结构中的归并排序。我们接下来通过维基百科中介绍归并排序。归并排序(Merge sort/mergesort),是创建在归并操作上的一种有效的排序算法,效率为O(nlogn)。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。首先采用分治法中的分割(递归地把当前序列平均分割成两半)和集成(在保持元素顺序的同时将上一步得到的子序列集成到一起(归并))。归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。 递归法(Top-down) 的过程如下: 1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 2、设定两个指针,最初位置分别为两个已经排序序列的起始位置 3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下   一位置 4、重复步骤3直到某一指针到达序列尾 5、将另一序列剩下的所有元素直接复制到合并序列尾 迭代法(Bottom-up) 的原理实现如下(假设序列共有n个元素): 1、将序列每相邻两个数字进行归并操作,形成ceil(n/2)个序列,排序后每个序列包含   两/一个元素 2、若此时序列数不是1个则将上述序列再次归并,形成ceil(n/4)个序列,每个序列包含四   /三个元素 3、重复步骤2,直到所有元素排序完毕,即序列数为1   如下图是归并排序的动画演示图(该图来自五分钟学会一个很有用的排序:归并排序):

  接下来是归并排序过程示意静态图:

  由于归并排序是分治算法的一个典型应用,接下来我们可以具体通过图来演示其分治过程。

2、具体实现   其实就是暴力破解法,双指针遍历,每遍历一个数,判断这个数与其前面的数组成逆序对的对数(防止重复计算),时间复杂度O(N^2)。这显然是不行的。因此我们的减少时间复杂度,想到了我们前面的介绍的归并排序。其时间复杂度为O(nlogn),不过需要我们注意的是归并排序中的临时数组本来是放在merge方法中,我们可以将其放在InversePairs方法中,减少临时变量消耗的时间和空间。其操作过程如下图所示:

  具体我分别用java和python来实现。首先我们用java将其实现:

public class Solution {
    int count = 0;
    public int InversePairs(int [] array) {
        if(array != null && array.length != 0){
            int[] tmpArr = new int[array.length];
            mergeSort(array,0,array.length-1,tmpArr);
        }
        return count;
    }
    private void mergeSort(int[] array, int start,int end,int[] tmpArr){
        if(start >= end){
            return;
        }
        int mid = (start+end)>>1;
        mergeSort(array,start,mid,tmpArr);
        mergeSort(array,mid+1,end,tmpArr);
        merge(array,start,mid,end,tmpArr);
    }
    private void merge(int[] array,int start,int mid,int end,int[] tmpArr) {
        int tmpIndex = start;
        int start2 = mid + 1;
        int i = start;
        while (start <= mid && start2 <= end) {
            if (array[start] > array[start2]) {
                tmpArr[tmpIndex++] = array[start++];
                count = (count + end - start2+1) % 1000000007;
            } else {
                tmpArr[tmpIndex++] = array[start2++];
            }
        }
        if (start2 <= end) {
            System.arraycopy(array, start2, tmpArr, tmpIndex, end - start2 + 1);
        }
        if (start <= mid) {
            System.arraycopy(array, start, tmpArr, tmpIndex, mid - start + 1);
        }
        System.arraycopy(tmpArr, i, array, i, end - i + 1);
    }
}

代码效果图如图所示:

接着我们用python实现:

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.count = 0
         
    def InversePairs(self, data):
        # write code here
        self.MergeSort(data)
        return self.count%1000000007
     
    def MergeSort(self,lists):
        #global count
        if len(lists) <= 1:
            return lists
        num = int( len(lists)/2 )
        left = self.MergeSort(lists[:num])
        right = self.MergeSort(lists[num:])
        r, l=0, 0
        result=[]
        while l<len(left) and r<len(right):
            if left[l] < right[r]:
                result.append(left[l])
                l += 1
            else:
                result.append(right[r])
                r += 1
                self.count += len(left)-l
        result += right[r:]
        result += left[l:]
        return result

代码效果图如图所示:

总结

  本道题主要考察数组两个数的逆序对求和,关键是对P%1000000007求模。刚开始的时候,以为本题很简单,因此用了最暴力的方法,但是时间复杂度很高,因此,我们转换了一种思路,用到了典型的归并排序,我们也介绍了归并排序的基本算法,并且用了java和python两种方法来将其实现。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] 飞飞晗 [2] 木阿南二三记 [3] 归并排序