蛮力:
遍历数组,对每个元素都往前遍历所有元素,如果有发现比它小的元素,就count++。
最后返回count取模。
结果没问题,但超时哈哈哈,只能过50%。
 
归并法:
看讨论,知道了这道题的经典做法应该是用归并的思想,之所以用归并,是因为像上面我们直接比较a[i]后面的所有元素的话,肯定是O(n^2)的,那么就先考虑它旁边周围的元素,然后就归并了。
 
好像之前也就写过归并的伪代码,所以我看了下大概思路,然后就开始写了,一开始是每次mergeSort方法里面,就要递归用的那个方法里面,都new两个temp数组,然后分别丢这两个数组给递归的sort方法,然后再把这两个排好序的数组合并到原来的数组a里面去。
 
测试的结果是通过了,但还是卡在百分之50……而且没给什么报错原因??50%数字很多也很大,给出的数组个数屏幕都不够显示……有个查看全部按钮可以在另一个页面查看数据。
于是我复制了这个挂了的数据,还有别人通过的方法。
测了下,结果一样啊????想了下可能是空间复杂度超了……毕竟我这里每次递归都搞了个数组……按道理这个归并排序应该是O(n)空间复杂度的。
 
上一下这个初始版本,也就是用了好多辅助数组的归并吧:
public class Solution {
private int count = 0; /*讨论说可以用分治法——归并排序的思路喔*/
public int InversePairs(int [] array) {
if(array.length <= 0)return 0;
mergeSortAndCount(array);
return count % 1000000007;
} private void mergeSortAndCount(int[] a) {
if(a.length == 1)return;
int[] b, c;
if(a.length % 2 == 0) {//数组元素个数为偶数的情况
b = new int[a.length / 2];
c = new int[a.length / 2];
for(int i = 0, j = a.length / 2, k = 0; i < a.length / 2 && j < a.length; i++, j++, k++) {
b[k] = a[i];
c[k] = a[j];
}
} else {
b = new int[a.length / 2 + 1];
c = new int[a.length / 2];
for(int i = 0, j = a.length / 2 + 1, k = 0; i <= a.length / 2 || j < a.length; i++, j++, k++) {
//这里b数组要初始化的长度是长一点的
b[k] = a[i];
if(j < a.length)c[k] = a[j];
}
} mergeSortAndCount(b);
mergeSortAndCount(c); mergeAndCount(b, c, a);//把已经排好序的b和c数组合并并且计算逆序,合并到a数组中去。 } private void mergeAndCount(int[] b, int[] c, int[] a) {
int i = 0, j = 0, k = 0;
while(i < b.length && j < c.length) {
if(b[i] <= c[j]) {//正常情况,非逆序
a[k ++] = b[i ++];
} else {//b的元素大于c的情况,逆序
count = count + b.length - i;//因为这个数组是排好序的,b[i]元素大于c[j],意味着b[i]-b[a.length - 1]的元素都大于它
a[k ++] = c[j ++];
}
}
while(i < b.length)a[k ++] = b[i ++];
while(j < c.length)a[k ++] = c[j ++];
}
}

改造后的归并:

这里全程就用到一个辅助数组,和一个本身就要排序的数组。

内部在处理递归的时候用的都是index,然后要说明的是,辅助数组和排序数组的身份是互换的。本来应该是:在原数组的两部分排序好的数组要合并嘛,就借一个辅助数组放合并的数据,然后再复制回原来的数组。

但现在我们用了一个整数i,来交替完成这个过程,反正只要最后一次合并是合并到要排序的那个数组就好了嘛。

这里有个也是只用了一个辅助数组,但不是交替用的例子,这个其实好理解点hh:

https://blog.csdn.net/abc7845129630/article/details/52740746

然后上代码:

public class Solution {
private long count = 0;//类变量,逆序对的数量 /*减少辅助数组的归并法,或者说只用一个辅助数组的归并法*/
public int InversePairs(int [] array) {
if(array.length <= 0)return 0;
int i = 0;
int[] help = new int[array.length];//辅助数组,就用这一个就够了,所以空间复杂度为O(n)
mergeAndCountSort(array, 0, array.length - 1, i, help);
return (int)(count % 1000000007);
} /**
* 对a数组中的begin到end元素进行归并排序,如果i是奇数则排序后的数组合并到b数组中去(b[begin-end]有序);否则就存到a数组中去(a[begin-end]有序)
* 之所以这样做是因为可以避免利用了辅助数组后还有复制元素到原来数组的操作
* 因为我们最后要排序的是a数组嘛,所以在主程序中调用这个sort应该传i=0或者是其他偶数
* @param a
* @param begin
* @param end
* @param i
* @param b
*/
private void mergeAndCountSort(int[] a, int begin, int end, int i, int[] b) {
if (begin == end) {// 递归终结条件
// 一开始是没有加这句的,如果排序的数组是偶数个的话,就没事,奇数个的话就有事。
if (i % 2 == 1) b[begin] = a[begin];// 奇数,这个结果要到b数组中去,否则不用动
} else { int middle = (begin + end) / 2;// 中间index // 分别对左半部分和右半部分做递归的归并排序,i + 1保证了下次用另一个数组做辅助数组
mergeAndCountSort(a, begin, middle, i + 1, b);
mergeAndCountSort(a, middle + 1, end, i + 1, b); if (i % 2 == 1) {
// i是奇数,那么归并后数组要合并到b数组中去
mergeAndCount(a, begin, middle, end, b);
} else {
// i是偶数,那么归并后的数组要合并到a数组去
mergeAndCount(b, begin, middle, end, a);
}
}
} /**
* 带有数逆序对的合并有序数组的方法,合并的过程中顺便数逆序对
* 将a数组中的[begin-middle]有序对和[middle + 1-end]有序对合并,合并结果到result数组中去,result数组的[begin-end]有序
* @param a
* @param begin
* @param middle
* @param end
* @param result
*/
private void mergeAndCount(int[] a, int begin, int middle, int end, int[] result) {
int i = begin, j = middle + 1, k = begin;
while(i <= middle && j <= end) {
if(a[i] <= a[j]) {
//左边的元素小于右边的,正常情况,不是逆序对
result[k++] = a[i++];
} else {
//左边的元素大于右边的,逆序情况 //因为这个数组是排好序的,所以意味着左边部分后面的元素也大于这个右边部分的这个元素,然后这里要提前加上去
//因为小的元素要被加到result中去,等等其他的就不会碰到它了
count = count + (middle - i + 1);
result[k++] = a[j++]; }
} while(i <= middle) result[k++] = a[i++];
while(j <= end) result[k++] = a[j++];
}
}

我刚改完这个归并的时候,在本地跑可以,然后排序也有效果,结果特么还是一样的结果????

%50一样卡在那个位置……我佛了。

查了很久,结合那个数据猜发现,这个给的例子就算是在新的网页中打开,也是还没结束的:

也就是说,这个逆序对的个数,应该是可以超过Int的范围的……

所以就像上面贴的代码一样,把int的count改成long,返回的时候再转换成int才通过。

这里讲一下这个归并法的一些要注意的地方:

1. 就是刚刚说的,逆序个数会大于int的最大值的问题,要用long。

2. 在递归中,本来begin == end就可以返回了,因为就有序了嘛,但这里因为涉及到复制来复制去,还有加个i的判断,如果为奇数要把这个数搞到b数组中去。(测试过,如果待排序的数字个数是偶数个好像就没有问题,是奇数个就会有问题)

3. 就是在数逆序对的时候的问题了,因为是排好序的,而且小的那个会进入目标数组排队,所以发现了左边的元素大于右边的元素时,不是只count++:

else {
//左边的元素大于右边的,逆序情况 //因为这个数组是排好序的,所以意味着左边部分后面的元素也大于这个右边部分的这个元素,然后这里要提前加上去
//因为小的元素要被加到result中去,等等其他的就不会碰到它了
count = count + (middle - i + 1);

剑指Offer——数组中的逆序对(归并排序的应用)的更多相关文章

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

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

  2. 剑指Offer——数组中的逆序对

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

  3. 用js刷剑指offer(数组中的逆序对)

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

  4. 剑指 Offer——数组中的逆序对

    1. 题目 2. 解答 借助于归并排序的分治思想,在每次合并的时候统计逆序对.因为要合并的两个数组都是有序的,如果左半部分数组当前值大于右半部分数组当前值,那么左半部分数组当前值右边的数就都大于右半部 ...

  5. 剑指offer_数组中的逆序对

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

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

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

  7. 剑指Offer34 数组中的逆序对

    /************************************************************************* > File Name: 34_Invers ...

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

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

  9. 剑指offer-数组中的逆序对-数组-python

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

随机推荐

  1. git导入项目

    远程仓库已经存在,使用的是gitblit,作为终端eclipse如何从中拷贝代码呢? 0.准备工作,windows->preference->team->git->config ...

  2. BZOJ4009:[HNOI2015]接水果

    浅谈树状数组与线段树:https://www.cnblogs.com/AKMer/p/9946944.html 题目传送门:https://www.lydsy.com/JudgeOnline/prob ...

  3. bzoj 2178 圆的面积并 —— 辛普森积分

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2178 先看到这篇博客:https://www.cnblogs.com/heisenberg- ...

  4. 模拟Spring中applicationContext.xml配置文件初始化bean的过程

    package com.xiaohao.action; import java.io.File; import java.lang.reflect.Method; import java.util.C ...

  5. ruby on rails 环境搭建步骤

    1.安装ruby ruby的下载页面一个版本有3样要下载的,帮助文件和安装文件.还有一个mingw. 安装时抛出make出错信息就是由于没有安装mingw引起的 到下载页http://rubyforg ...

  6. ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证【转载】

    http://www.cnblogs.com/dotNETCoreSG/p/aspnetcore-4_2_2-validation.html 介绍模型验证 在一个应用程序将数据存储到数据库之前,这个应 ...

  7. shell入门-shell特性

    1.关于! 命令:!! 说明: 执行上一条命令 [root@wangshaojun ~]# pwd/root[root@wangshaojun ~]# !!pwd/root 命令:!n (n表示数字) ...

  8. hadoop mapreduce 计算平均气温的代码,绝对原创

    1901 46 1902 21 1903 48 1904 33 1905 43 1906 47 1907 31 1908 28 1909 26 1910 35 1911 30 1912 16 1913 ...

  9. ubuntu 和 CentOS 安装docker

    一.ubuntu14.04 安装docker 1 apt-get update2 apt-get update-grup3 reboot4 检查mapper的安装:ls -l /sys/class/m ...

  10. p2661 信息传递(Tarjan模板)

    传送门 题目 有 nnn 个同学(编号为 111 到 nnn )正在玩一个信息传递的游戏.在游戏里每人都有一个固定的信息传递对象,其中,编号为 iii 的同学的信息传递对象是编号为 TiT_iTi​ ...