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!!!
参考文献
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- WPF加载中实现
- Chrome Headless 尝试
- MySQL--索引及优化查询
- Nginx 配置相关--Gzip压缩、CORS
- Webpack+Vue2项目结构生成
- 关于跨域以及Spring Boot的解决方案
- FFmpeg进行音频的解码和播放
- Gitbook 安装及使用
- MySQL 视图、过程、函数
- 基于Spring Boot + Dubbo的全链路日志追踪(二)
- 基于Spring Boot + Dubbo的全链路日志追踪(一)
- 使用C语言编写Python扩展包
- PlantUML基本使用(一)--时序图
- gRPC基本使用(一)--java与go之间的相互调用
- confd基本使用--Nginx配置自动化