首先先上LeetCode今天的每日一题(面试题51. 数组中的逆序对):

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

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

示例1

  由于题目中已经给出数组长度为: 0 <= 数组长度 <= 50000, 所以如果单纯使用两个for循环(时间复杂度为 $O\left ( n^{2} \right )$ 暴力求解的话是一定会超时的。

  在这里可以使用归并排序,并同时得出逆序对的总数,其中归并排序使用的是“分治法”,所以时间复杂度为 $O\left ( nlogn \right )$ ,而计算逆序对只需要在每次循环中进行一次计算,所以相当于在其中增加 $O\left ( 1 \right )$ 的时间复杂度,所以时间复杂度并不会变化,这个在后面会对计算方法会有详细的介绍,因为一开始自己写的时候也有在这里卡住

  如下是归并排序的示意图,图是盗来的,但是觉得做的真的是太好看了,而且清楚明了,以下超链接为引用的原博客网址(https://www.cnblogs.com/chengxiao/p/6194356.html):

  相信看了这张图之后,整个归并排序的算法就已经非常清楚明了了,如下代码是只对于归并排序的实现,使用的是递归的方法:

public class mergeSort {
public static void main(String args[]) {
mergeSort a = new mergeSort();
int[] numbers = new int[] {7,2,5,2,6,3,4,8};
a.merge(0, numbers.length-1, numbers);
for (int i : numbers) {
System.out.println(i);
}
}
public void merge(int left, int right, int[] numbers) {
if(left < right) {
int mid = (left + right)/2;
merge(left, mid, numbers);
merge(mid+1, right, numbers);;
mergeSort(left, right, numbers);
}
}
public void mergeSort(int left, int right, int[] numbers) {
//将数组分为左右两个部分,分别为[left, mid]和[mid+1, right]
int mid = (left + right)/2;
int i = left;
int j = mid + 1;
int[] temp = new int[right - left + 1];
for(int k = 0 ; k < temp.length; k++) {
//考虑如果数组左边已经到达尾端,则只需要将右边数组依次放入temp数组即可
if(i == mid + 1) {
temp[k] = numbers[j];
j++;
}
//考虑如果数组右边已经到达尾端,则只需要将左边数组依次放入temp数组即可
else if(j == right + 1) {
temp[k] = numbers[i];
i++;
}
//如果左边数组指向的数字大于右边数组指向的数字,则将右边数组指向的数字放入temp数组当中
else if(numbers[i] > numbers[j]) {
temp[k] = numbers[j];
j++;
}
//反之亦然
else {
temp[k] = numbers[i];
i++;
}
}
//最后将排好序的temp数组重新放入原数组当中,记得起始位置是从numbers数组的left开始,而不是0
for(int m = left, k = 0; m <= right; m++, k++) {
numbers[m] = temp[k];
}
}
} //最终结果为:2,2,3,4,5,6,7,8

归并排序的实现

  那么,如何来计算出逆序对呢?那么我们就要思考,为什么在归并排序中就能计算出逆序对的数量。这就要观察每次用来排序的数组的特点了,由于排序是由从两个长度为1的数组开始进行的,所以就可以保证在每一次的递归过程中,我们需要进行排序的数组一定会有以下规律,即:

  1. 将要排序的数组number的左右两个部分一定都是已经分别排好序了的,例如上图中需要排序的数组[4,5,7,8,1,2,3,6], 将这个数组分为左右两个部分[4,5,7,8]和[1,2,3,6],这两个数组是一定已经排好顺序了的。

  2. 每个数字与其他数组都会正好比一次大小,例如上图中的数字4,它在这次的统计中,会跟1,2,3,6比,会发现有3组逆序对,而在那之后,这个4就再也不会跟这4给数字进行比较了,也就不会产生重复。

  这时,你一定会问,那前面的5,7,8又是在什么时候进行比较的呢?其实在上一步,即当大的数组为[4,5,7,8]的时候,4就已经和7,8进行了比较,而在更前一步,4就和5进行了比较,所以就可以完美的不重复不遗漏统计所有数字的逆序对了。

  既然不会重复,那我们也就只需要有一个计数器count来记录产生的逆序对的数量就行了,那么,怎么来计算这个逆序对的数量呢?

  我一开始的错误想法是,左边数组的每一个数字和右边数字进行比较的时候,如果发现左边数字大于右边,那么count就+1,但发现统计的数量总是少于实际值,后来才发现了原因:

  例如数组[2,2,4,53,,6,8], 将其看为左右两个部分,可以发现当左边数组指针指向5的时候,右边数组的指针已经指向4了,那么其实本来数组中的5和3是并没有进行比较的,因此就会出现漏数的情况。

  在观察了很久之后,终于发现了其中的规律:

  假设左边数组指针为 $i$ , 右边数组指针为 $j$ , 如果发现 $ numbers[i] > numbers[j]$ , 那么对于右边数组的这个数字 $numbers[j]$ ,一定有左边数组的 $[i, mid]$ 位置的数字都会大于这个数字,因为这里两边的数组都是递增的,所以我们只需要每次发现 $numbers[i] > numbers[j]$ 之后,用 $count = count + (mid - i + 1)$ 统计即可。

  注意:为了统计count的数量,一定要将方法中的返回值类型从 void 变为 int, 因为如果只是利用void方法传参的话,count的值是不会改变的。(如有错误,欢迎指正,应该是这个样子的吧)

  最终利用归并排序计算逆序对的代码实现如下:

public class Merge_Sort {
public static void main(String args[]) {
Merge_Sort a = new Merge_Sort();
int[] numbers = new int[] {4,2,5,2,6,3,4,8};
int b = a.merge(0, numbers.length-1, numbers);
System.out.println(b);
}
public int merge(int left, int right, int[] numbers) {
if(left < right) {
int mid = (left + right)/2;
int count = merge(left, mid, numbers) + merge(mid+1, right, numbers);;
return mergeSort(left, right, numbers, count);
}
return 0; }
public int mergeSort(int left, int right, int[] numbers, int count) {
int mid = (left + right)/2;
int i = left;
int j = mid + 1;
int[] temp = new int[right - left + 1];
for(int k = 0 ; k < temp.length; k++) {
if(i == mid + 1) {
temp[k] = numbers[j];
j++;
}
else if(j == right + 1) {
temp[k] = numbers[i];
i++;
}
else if(numbers[i] > numbers[j]) {
temp[k] = numbers[j];
j++;
//count计数代码添加如下
count = count + (mid - i + 1);
}
else {
temp[k] = numbers[i];
i++;
} }
for(int m = left, k = 0; m <= right; m++, k++) {
numbers[m] = temp[k];
}
return count;
}
}
//输出结果为8

MergeSort归并排序和利用归并排序计算出数组中的逆序对的更多相关文章

  1. 归并排序(归并排序求逆序对数)--16--归并排序--Leetcode面试题51.数组中的逆序对

    面试题51. 数组中的逆序对 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 示例 1: 输入: [7,5,6,4] 输出 ...

  2. 力扣Leetcode 面试题51. 数组中的逆序对 - 归并排序

    在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 示例 1: 输入: [7,5,6,4] 输出: 5 限制: 0 <= ...

  3. 九度OJ 1348:数组中的逆序对 (排序、归并排序)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2777 解决:656 题目描述: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组 ...

  4. php实现数组中的逆序对(归并排序实现:排序 辅助数组)

    php实现数组中的逆序对(归并排序实现:排序 辅助数组) 一.总结 这题用归并排序  线段树   树状数组 等操作的复杂度应该都是小于n方的 二.php实现数组中的逆序对 题目描述 在数组中的两个数字 ...

  5. 剑指 Offer 51. 数组中的逆序对 + 归并排序 + 树状数组

    剑指 Offer 51. 数组中的逆序对 Offer_51 题目描述 方法一:暴力法(双层循环,超时) package com.walegarrett.offer; /** * @Author Wal ...

  6. 九度OJ 1348 数组中的逆序对 -- 归并排序

    题目地址:http://ac.jobdu.com/problem.php?pid=1348 题目描述: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求 ...

  7. 剑指Offer-34.数组中的逆序对(C++/Java)

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

  8. [剑指OFFER] 数组中的逆序对

    题目描述 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数.     分析:利用归并排序的思想,分成2部分,每一部分按照从大到 ...

  9. 数组中的逆序对(python)

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

随机推荐

  1. 微服务交付至kubernetes流程

    目录 1.微服务简介 2.K8s部署微服务考虑的问题 3.项目迁移到k8s流程 1.微服务简介 微服务优点 服务组件化 每个服务独立开发.部署,有效避免一个服务的修改引起整个系统重新部署 技术栈灵活 ...

  2. 《JavaScript 模式》读书笔记(5)— 对象创建模式4

    我们学完了大部分对象创建模式相关的内容,下面还有一些小而精的部分. 七.对象常量 JavaScript中没有常量的概念,虽然许多现代的编程环境可能为您提供了用以创建常量的const语句.作为一种变通方 ...

  3. Redis数据结构——quicklist

    之前的文章我们曾总结到了Redis数据结构--链表和Redis数据结构--压缩列表这两种数据结构,他们是Redis List(列表)对象的底层实现方式.但是考虑到链表的附加空间相对太高,prev 和 ...

  4. JVM tomcat 性能调优

    1,新建web 测试项目并且发布到Tomcat,访问路径:http://127.0.0.1:8080/JvmWeb/index @WebServlet("/index") publ ...

  5. 记录---java中jsp页面引入jquery路径的问题

    今天在jsp页面中引入jquery的时候因为路径不对总是报404,网上的方法找到几种试了试但是最后结果还是不生效,遂想起原先的项目中有引入外部jquery的例子,所以立马看了看,发现当时的项目中是用$ ...

  6. [vijos]1066弱弱的战壕<线段树>

    题目链接:https://www.vijos.org/p/1066 这道题没什么难度,只是要一个排序然后就是线段树的基本套路模版了 但是我还是讲一讲思路吧: 给出的是坐标x,y,当一个点的x,y都小于 ...

  7. 五、【Docker笔记】Dockers仓库

    仓库是集中存放镜像的地方,仓库的概念不要与注册服务器做混淆.注册服务器是存放仓库的具体服务器,每个服务器上可能有多个仓库,一个仓库有多个镜像. 仓库又可分为共有仓库和私有仓库,最大的共有仓库即Dock ...

  8. Docker+Jmeter+InfluxDB+Grafana搭建性能测试监控平台

    搭建需求? jmeter自身的聚合测试报告可视化效果极差,为更加形象的.动态的展示测试过程,需要一个具有时序性的可视区来展示给我们的测试者, 这时候就需要用到后端监控,下面我们来开始搭建符合这种测试需 ...

  9. 【图机器学习】cs224w Lecture 7 - 节点的表示

    目录 Node Embedding Random Walk node2vec TransE Embedding Entire Graph Anonymous Walk Reference 转自本人:h ...

  10. web系统安全运营之基础- 基于DFA算法的高性能的敏感词,脏词的检测过滤算法类(c#).

    [概述]做好一个web系统的安全运维,除了常规的防注入,防入侵等,还有一个检测并过滤敏感词,脏词..  这件事做得不好,轻则导致一场投诉或纠纷,重则导致产品被勒令关闭停运. 废话少说,先看下代码,可以 ...