这个题目挺拗口的,还是先来看这个问题的描述吧!详细也可以查看这个帖子《 facebook一道面试题,求效率算法 》。因为本文是一边想一边写的,难免啰嗦,闭门造车和错误,请不吝赐教!

/* 矩阵A 68 36 22 59 77 39 81 20 17 将矩阵A每列排序之后(升序排列)应该得到的矩阵是: 矩阵B: 59 20 17 68 36 22 81 77 39 这样矩阵A中每个元素在矩阵B中对应的位置就是 矩阵C: 2 3 3 1 1 1 3 2 2 问:由矩阵A得到矩阵C的算法? */

面对这个问题,我想需要解决两个问题。一是如何对于矩阵A的列进行排序;二是如何返回排序后每一列元素各自原来的位置。结合问题来看,也就是如何由A,求矩阵B和由B求矩阵C的过程。

就事论事,从常规角度出发,由A求B,并不难。无非就是提取出每一列,针对该列排序,然后返回矩阵B;然后该怎么做呢?如何由B求C呢?因为矩阵B本身没有参照,所以需要和A进行比较,来得到原始的位置序号。因此,可以想象,这种常规做法不会有比较好的时间复杂度,而且算法也比较繁琐。

这个帖子里,9#和12#两位仁兄的办法很可取!将矩阵A里的数值型的元素,转换成具有维度特征的对象型元素。这样再由A求B的时候,矩阵B中的每一元素就会保持有原矩阵的特征,从而直接由B求出C。顺着这个思路,先来构造我们需要的对象类Element。

public class Element implements Comparable<Element>{ private int value; private int row; public Element(){ } public Element(int value, int row){ this.value = value; this.row = row; } public int getValue(){ return this.value; } public int getRow(){ return this.row; } public int compareTo(Element e) { if (this.getValue() > e.getValue()) return 1; else if(this.getValue() == e.getValue()) return 0; else return -1; } }

接下来,便是构造这个Element的对象数组。

private static Element[][] elementMatrix; public static void transfer(int[][] source){ int rows = source.length; int columns = source[0].length; elementMatrix = new Element[rows][columns]; for(int i=0; i<rows; i++){ for(int j=0; j<columns; j++) elementMatrix[i][j] = new Element(sourceMatrix[i][j], i+1); //注意这里是i+1 } }

既然有了这个对象数组,那么接下来的就是随我们任意蹂躏了!

public static void sortElementArray(){ int rows = elementMatrix.length; int columns = elementMatrix[0].length; List<List<Element>> outList = new ArrayList<List<Element>>(); for(int i=0; i<columns; i++){ List<Element> innerList = new ArrayList<Element>(); for(int j=0; j<rows; j++) innerList.add(elementMatrix[j][i]); Collections.sort(innerList);//排序后再放入到outlist中 outList.add(innerList); } for(int i=0; i<rows; i++){ for(int j=0; j<columns; j++) index[i][j] = outList.get(j).get(i).getRow();//将序号复制到index二维数组中去 } }

这段排序算法,也就是整个程序中时间复杂度最高的部分。双层for循环的时间复杂度是m*n,而Collections.sort()方法是实现的归并排序,时间复杂度是m*log(m)。因为排序算法在第一层for循环内,所以这个方法的时间复杂度是(m^2)*log(m)。下面贴出完整代码及输出:

import java.util.*; public class Matrix { private static int[][] sourceMatrix = {{68, 36, 22}, {59, 77, 39}, {81, 20, 17}}; private static int[][] index = sourceMatrix; private static Element[][] elementMatrix; public static void transfer(int[][] source){ int rows = source.length; int columns = source[0].length; elementMatrix = new Element[rows][columns]; for(int i=0; i<rows; i++){ for(int j=0; j<columns; j++) elementMatrix[i][j] = new Element(sourceMatrix[i][j], i+1); //注意这里是i+1 } } public static void sortElementArray(){ int rows = elementMatrix.length; int columns = elementMatrix[0].length; List<List<Element>> outList = new ArrayList<List<Element>>(); for(int i=0; i<columns; i++){ List<Element> innerList = new ArrayList<Element>(); for(int j=0; j<rows; j++) innerList.add(elementMatrix[j][i]); Collections.sort(innerList);//排序后再放入到outlist中 outList.add(innerList); } for(int i=0; i<rows; i++){ for(int j=0; j<columns; j++) index[i][j] = outList.get(j).get(i).getRow();//将序号复制到index二维数组中去 } } public static void main(String arsg[]){ transfer(sourceMatrix); sortElementArray(); for(int i=0; i<index.length; i++){ for(int j=0; j<index[i].length; j++) System.out.print(index[i][j] +" "); System.out.println(); } } }

2 3 3 1 1 1 3 2 2 //原帖中的那个输出有问题,后面两列应该是3,1,2

好,到此为止,这个问题应该就算解决了,如果就事论事的话。 接下来,我们再来考验下这段代码的适应能力,也就是来测试下这段代码。因为代码中的sourceMatrix是个很理想的3*3的方阵。先测试下3*4和4*3的规则二维矩阵吧!

sourceMatrix = {{68, 36, 22, 8}, {59, 77, 39, 7}, {81, 20, 17, 6}};//3*4 sourceMatrix = {{68, 36, 22}, {59, 77, 39}, {81, 20, 17}, {1, 2, 3}};//4*3

测试结果:

2 3 3 3 1 1 1 2 3 2 2 1 4 4 4 2 3 3 1 1 1 3 2 2

完全没有问题!输出数组的维度和源数组是一样的,而且也和手算结果一致。包括代码中的一组数据和测试的两组数据,一共3组数据。这3组数据有一个共同点,就是都是规则的数组(下文中也称为规则矩阵(其实,数学上的矩阵都是严格规则的。)),也就是说数组里的每一行和每一列都是等长的。接下来我们来看下一组不规则数组的测试情况:

sourceMatrix = {{68, 36, 22}, {59, 77}, {81, 20}}; //输出 /* Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2 at Matrix.transfer(Matrix.java:16) at Matrix.main(Matrix.java:41) */

如果你仔细看了完整代码的话,对于这个错误应该不会意外。虽然jvm的错误说错误发生在第16行,但是造成这个错误根源却是在"int columns = source[0].length;",因为针对这个数组,那么source[0]的长度是3,但是source[1]和source[2]的长度却只有2。因此,会有数组越界错误。但是因为Java里的数组在使用前都要预定义大小的。因此摆在我们面前有2条路:

1. 按照列长度的最大值来初始化elementMatrix数组。也就是说将不规则矩阵填充为规则的m*n阶矩阵。

2. 精确匹配source数组,来够造elementMatrix数组。

大概地想一下:方法1,首先会消耗多余空间。极端的情况,比如稀疏矩阵中个别列很长,但是有很多列很短,这样大量的空间会被浪费;其次,如何填充为规则矩阵?也就是说拿什么数字或者标示来填充原本不存在的位置?第三,引入额外的数字或标示对于排序的影响是无法估计的。方法2,将source数组遍历并构造成Element对象,然后在对行排序,之后在获取排序后的序号并输出index数组。综合比较方法1和2可以看出,2更具可行性。接下来继续分析,如果我们能够获取 elementMatrix数组,那么如何针对行来进行排序?这个时候又有2条思路:

1. 遍历一行,然后排序,再返回index;

2. 获取整个数组,先按行排序,相同行按值的大小排序。

把数组里的每一行遍历到list里或者一个新的数组里排序,如果这个数组是m*n的话,那么针对每一行的m个元素,排序的时间复杂度是m*log(m),共有n行,那么总的时间复杂度就是n*m*log(m)。 接下来,看看方法2一口气把数组内的全部元素先进行行大小比较,再比较值大小的情况。二维m*n阶的数组最多有m*n个数,那么排序的话,复杂度是m*n*log(m*n),比针对每一行排序慢。到目前为止有两条可行的排序算法,分别是针对每一行的排序算法,时间复杂度是 m*n*log(m);和针对整个数组的排序,时间复杂度是 m*n*log(m*n)。光从这一点上看,方法1要优于方法2。但是,方法1有一个比较大的问题,就是排序方法并不是整个算法中最耗时的地方。想一下,大致的流程是这样的:获取每一行元素方法a,排序方法b,接下来问题就来了,按照列数进行for循环的话,方法a和方法b都要被循环n次。前面提到了排序算法是的时间复杂度是 n*m*log(m),但是获取每行元素是双层for循环,这样一乘就是m*n^2。所以,方法1的排序最优(因为元素少),但是总体算法的时间复杂度高于方法2。因此,方法2更优。

说了这么多,如何针对数组内元素进行行排列之后,再进行数值比较呢?接下来重写一下Element类,取名为AdvancedElement,以示区分:

public class AdvancedElement implements Comparable<AdvancedElement>{ private int value; private int row; private int column; public AdvancedElement(){ } public AdvancedElement(int value, int row, int column){ this.value = value; this.row = row; this.column = column; } public int getValue(){ return this.value; } public int getRow(){ return this.row; } public int getColumn(){ return this.column; } public int compareTo(AdvancedElement e) { //排序规则是行号小的在前,行号相同则比较value if(this.getColumn() != e.getColumn()) return (int) this.getColumn() - e.getColumn(); else{ if (this.getValue() > e.getValue()) return 1; else if(this.getValue() == e.getValue()) return 0; else return -1; } } }

然后便是利用AdvancedElement类,对于不规则矩阵(当然也能处理规则矩阵)的求解算法,这里为了让AdvancedMatrix类更有点工具类的意思,所以方法和变量设置并不像之前Matrix类(当然,并没有引入泛型。):

import java.util.*; public class AdvancedMatrix { private static List<AdvancedElement> list; private static void transfer(int[][] source){ list = new ArrayList<AdvancedElement>(); for(int i=0; i<source.length; i++){ for(int j=0; j<source[i].length; j++) list.add(new AdvancedElement(source[i][j], i+1, j)); } } private static int getRowLength(int[][] source){ int maxSize = source[0].length; for(int i=1; i<source.length; i++){ if(source[i].length > maxSize) maxSize = source[i].length; } return maxSize; } public static void getIndex(int[][] source){ int listCount = 0; transfer(source); Collections.sort(list); for(int column=0; column<getRowLength(source); column++){ int row = 0; while(row < source.length){ if(column >= source[row].length){ row++; continue; } source[row][column] = list.get(listCount).getRow(); listCount ++; row++; } } } }

为了验证前面的分析,这里也给出用方法1的代码:

import java.util.*; public class UltimateMatrix { private static List<List<AdvancedElement>> list = new ArrayList<List<AdvancedElement>>(); private static List<AdvancedElement> innerList; private static void getColumn(int[][] source){ innerList = new ArrayList<AdvancedElement>(); int listCount = 0; for(int column=0; column<getRowLength(source); column++){ int row = 0; while(row < source.length){ if(column >= source[row].length){ row++; continue; } innerList.add(new AdvancedElement(source[row][column], row+1, column)); listCount ++; row++; } } } private static int getRowLength(int[][] source){ int maxSize = source[0].length; for(int i=1; i<source.length; i++){ if(source[i].length > maxSize) maxSize = source[i].length; } return maxSize; } public static void getIndex(int[][] source){ int listCount = 0; int maxRowLength = getRowLength(source); for(int cloumn=0; cloumn<maxRowLength; cloumn++){ getColumn(source); Collections.sort(innerList); list.add(innerList); } for(int column=0; column<maxRowLength; column++){ int row = 0; while(row < source.length){ if(column >= source[row].length){ row++; continue; } source[row][column] = list.get(column).get(listCount).getRow(); listCount ++; row++; } } } }

接下来是对于这两个算法的测试,因为系统内还有其他因素影响程序执行,因此这种测试的结果只有参照意义。测试的策略是将一组算法运行10次,并取平均值。当然了,是不是10次就是合理的,还是那句话,测试只是参照。请看测试代码:

public class TestMatrix { public static void main(String args[]){ long sum = 0; for(int count=0; count<10; count++){ int[][] sourceMatrix = { {68, 36, 23, 45, 9, 19, 70, 33, 4, 12, 54, 67, 93, 19, 76}, {59, 77, 39, 1, 13, 27, 22, 76, 87, 8, 44, 21, 89, 92, 30}, {81, 20, 17, 19, 11, 333, 1, 12, 523, 33, 6565, 1312, 22}, {11119, 3, 212, 121, 223, 653, 22, 87, 4232, 768767, 434}, {59, 90, 89, 37}, {312, 313, 65, 757, 656, 34, 677, 88, 4, 23, 32, 23235}, {9}, {523, 33, 6565, 1312, 33, 5, 423, 31231, 66, 78, 888, 3213}, {313, 65, 757, 212, 121, 66, 333, 1, 12, 523}, {757, 656, 34, 677, 88, 4, 423, 12121, 78, 880, 74, 4232, 768767}, {33, 656, 34, 677, 31231, 66, 78, 888, 3213}, {123, 5, 23, 31231, 33, 5, 423, 31231, 66, 18, 888, 3213}, {423, 31231, 33, 5, 423, 31231, 5, 423, 31231, 33, 5, 423, 17, 19, 11, 333}, {4231, 51, 4253, 677, 88, 5, 423, 31231, 33, 5, 423, 31231, 66, 78, 888, 3213}, {1223, 5, 1423, 31231, 3, 310231, 76, 78, 88, 3213} }; long startTime = System.nanoTime(); AdvancedMatrix.getIndex(sourceMatrix); sum += System.nanoTime() - startTime; } System.out.println(sum/10); } }

得出的结果是:334585。单位是纳秒,呵呵~~也就是说针对这个数组,时间在6位数的纳秒这样一个数量级。把"AdvancedMatrix.getIndex(sourceMatrix);"换成"UltimateMatrix.getIndex(sourceMatrix);"再来看看方法1的时间数量级。输出为:1193217,是7位数的纳秒数量级。孰优孰劣,一目了然了!好了,AdvancedMatrix和AdvancedElement类已经能够解决规则矩阵和不规则矩阵针对行排序的需求了。

写了这么多,从原始的求二维规则数组的序列扩散到不规则数组,多想想挺有乐趣的。当然,如果你有更好的办法来解决规则&不规则矩阵的行排序问题,请拍砖!

关于返回二维数组排序后序号数组的问题求解相关推荐

  1. 知识点笔记(二维数组排序、统计数组重复个数、)

    //统计数组内重复元素的个数 let arr = ["leyi", "leyi", "leyi2", "leyi2", ...

  2. php获取二维数组前三条,php二维数组排序后获取最大值

    PHP一维数组的排序可以用sort(),asort(),arsort()等函数,但是PHP二维数组的排序需要自定义. 自定义: [code lang=" [/code] 最近在做一个数据分析 ...

  3. php 二维数组 下标,php 二维数组排序 按指定数组下标排序

    $array = array( array( 'id' => 1, 'name' => "语文", 'listorder' => 2 ), array( 'id' ...

  4. 【C 语言】二级指针作为输入 ( 二维数组 | 抽象业务函数 | 二维数组打印函数 | 二维数组排序函数 )

    文章目录 一.抽象 二维数组 业务函数 1.二维数组 打印函数 2.二维数组 排序函数 二.完整代码示例 一.抽象 二维数组 业务函数 1.二维数组 打印函数 注意 , 二维数组 作为 函数参数 输入 ...

  5. 【C 语言】二级指针作为输入 ( 二维数组 | 二维数组遍历 | 二维数组排序 )

    文章目录 一.二维数组 1.二维数组声明及初始化 2.二维数组遍历 3.二维数组排序 二.完整代码示例 一.二维数组 前几篇博客中 , 介绍的是指针数组 ; 指针数组 的 结构特征 是 , 数组中的每 ...

  6. PHP二维数组排序 array_multisort

    1.根据二维数组中的某一列按升序或者降序排列 不需要遍历数组就可以对二维数组排序 // 1.构造数组 $data = [['a'=>23,'b'=>'hhhh','c'=>'2013 ...

  7. 【C 语言】二级指针案例 ( 字符串切割 | 返回 二维数组 作为结果 )

    文章目录 一.二级指针案例 ( 返回二维数组 ) 二.完整代码示例 一.二级指针案例 ( 返回二维数组 ) 将 "12,ab,345," 字符串 以 逗号 "," ...

  8. php 初始二维数组长度,php二维数组排序与默认自然排序的方法介绍

    php二维数组排序函数,默认自然排序,即sort排序.这里可以指定按二维数组中的某个值进行多种方法排序,具体看下面的程序注释. 代码如下: /** * @function 二维数组自然排序 * @au ...

  9. c语言sort函数排序二维数组,js 二维数组排序sort()函数

    一.按数值排序 var arr = [[1, 2, 3], [7, 2, 3], [3, 2, 3]]; arr.sort(function(x, y){ return x[0] – y[0]; }) ...

最新文章

  1. 2016年十大存储预测
  2. Visual Assist使用详细说明
  3. 华为手机 android8.0APP更新时出现安装包解析异常的提示及安装闪退(无反应)问题
  4. hybris测试数据的存放位置
  5. java子类继承父类实际_java子类继承父类,是否生成父类对象?
  6. WEB数据挖掘(十)——Aperture数据抽取(6):在Aperture中使用RDF2Go
  7. Kudu的Using Apache Kudu with Apache Impala(官网推荐的步骤)
  8. 如何自动调整代码格式 - vim /Visual Studio/ Source Insight...
  9. 赛车游戏代码大全html,赛车游戏代码
  10. Code Combat 适合初中生、高中生 学习代码的游戏
  11. php转换ofd文件格式,一种OFD格式文档支持脚本的方法与流程
  12. Python训练自己的语音识别系统
  13. 苹果airplay是什么 苹果手机投屏到电脑
  14. 实验二 Linux下Vi编辑器的使用
  15. be 动词 、 一般动词的过去式
  16. 泛微oa流程表单之明细表下拉框事件
  17. 如何将谷歌浏览器设置为默认浏览器
  18. 悲催:一个80后程序员的爱情故事【视频】-但愿我不是那个陈旭阳!55...
  19. java+springboot学校小卖部超市收银系统maven
  20. python声纹识别_声纹识别算法、资源与应用(二)

热门文章

  1. 强人工智能基本问题:神经网络分层还是不分层
  2. 浅谈CIVIL 3D
  3. Webdriver for python 入门示例2(浏览器句柄操作)
  4. 相信大家看了这篇文章对Oracle如何工作有一个形象的了解!
  5. AD灾难恢复情景及方案
  6. 第六次作业--结对编程第二次
  7. An unknown error occurred.
  8. grumble.js
  9. R语言系列:多元统计分析简介
  10. 限定某个目录禁止解析php 限制user_agent php相关配置