万书网 > 文学作品 > 漫画算法:小灰的算法之旅 > 5.6无序数组排序后的最大相邻差

5.6无序数组排序后的最大相邻差





5.6.1 一道奇葩的面试题



下面我来考查你一道算法题,有一个无序整型数组……

题目

有一个无序整型数组,如何求出该数组排序后的任意两个相邻元素的最大差值?要求时间和空间复杂度尽可能低。

可能题目有点绕,让我们来看一个例子。

哦,让我想想……

嗨,这还不简单吗?先使用时间复杂度为O(nlogn)的排序算法给原来的数组排序,然后遍历数组,对每两个相邻元素求差,最大差值不就出来了吗?

解法1:

使用任意一种时间复杂度为O(nlogn)的排序算法(如快速排序)给原数组排序,然后遍历排好序的数组,并对每两个相邻元素求差,最终得到最大差值。

该解法的时间复杂度是O(nlogn),在不改变原数组的情况下,空间复杂度是O(n)。

唉,我出这样的题目,显然不是为了让你来排序的。你再想想,有没有更快的解法?

没有了呀。不排序的话还能怎么做呢?

呵呵,那你回家等通知去吧!



5.6.2 解题思路

小灰,你刚刚去面试了?结果怎么样?

唉……

大黄,我今天遇见一道怪题,怎样才能计算出无序数组排序后的最大相邻差值?

嗯……这道题确实很有意思。虽然对数组排序以后肯定能得到正确的结果,但我们没有必要真的去进行排序。

不排序的话,该怎么办呢?

小灰,你记不记得,有哪些排序算法的时间复杂度是线性的?

好像有计数排序、桶排序,还有个什么基数排序……可你刚才不是说不用排序吗?

别着急,我们仅仅是借助一下这些排序的思想而已。小灰你想一下,这道题能不能像计数排序一样,利用数组下标来解决?

像计数排序一样?让我想想啊……

有了!我可以使用计数排序的思想,先找出原数组最大值和最小值的差……

解法2:

1.  利用计数排序的思想,先求出原数组的最大值max与最小值min的区间长度k(k=max-min+1),以及偏移量d=min。

2.  创建一个长度为k的新数组Array。

3.  遍历原数组,每遍历一个元素,就把新数组Array对应下标的值+1。例如原数组元素的值为n,则将Array[n-min]的值加1。遍历结束后,Array的一部分元素值变成了1或更高的数值,一部分元素值仍然是0。

4.  遍历新数组Array,统计出Array中最大连续出现0值的次数+1,即为相邻元素最大差值。

例如给定一个无序数组  {  2,  6,  3,  4,  5,  10,  9  },处理过程如下图。

第1步,确定k(数组长度)和d(偏移量)。

第2步,创建数组。

第3步,遍历原数组,对号入座。

第4步,判断0值最多连续出现的次数,计算出最大相邻差。

很好,我们已经进步了很多。这个思路在数组元素差值不很悬殊的时候,确实效率很高。

可是设想一下,如果原数组只有3个元素:1、2、1  000  000,那就要创建长度是1  000  000的数组!想一想还能如何优化?

让我想想啊……

对了!桶排序的思想正好解决了这个问题!

解法3:

1.  利用桶排序的思想,根据原数组的长度n,创建出n个桶,每一个桶代表一个区间范围。其中第1个桶从原数组的最小值min开始,区间跨度是(max-min)/(n-1)。

2.  遍历原数组,把原数组每一个元素插入到对应的桶中,记录每一个桶的最大和最小值。

3.  遍历所有的桶,统计出每一个桶的最大值,和这个桶右侧非空桶的最小值的差,数值最大的差即为原数组排序后的相邻最大差值。

例如给出一个无序数组  {  2,  6,  3,  4,  5,  10,  9  },处理过程如下图。

第1步,根据原数组,创建桶,确定每个桶的区间范围。

第2步,遍历原数组,确定每个桶内的最大和最小值。

第3步,遍历所有的桶,找出最大相邻差。

这个方法不需要像标准桶排序那样给每一个桶内部进行排序,只需要记录桶内的最大和最小值即可,所以时间复杂度稳定在O(n)  。

很好,让我们来写一下代码吧。

好的,我试试。

1.  public  static  int  getMaxSortedDistance(int[]  array){

2.

3.  //1.得到数列的最大值和最小值

4.  int  max  =  array[0];

5.  int  min  =  array[0];

6.  for(int  i=1;  i
7.  if(array[i]  >  max)  {

8.  max  =  array[i];

9.  }

10.  if(array[i]  <  min)  {

11.  min  =  array[i];

12.  }

13.  }

14.  int  d  =  max  -  min;

15.  //如果max  和min  相等,说明数组所有元素都相等,返回0

16.  if(d  ==  0){


17.  return  0;

18.  }

19.

20.  //2.初始化桶

21.  int  bucketNum  =  array.length;

22.  Bucket[]  buckets  =  new  Bucket[bucketNum];

23.  for(int  i  =  0;  i  <  bucketNum;  i++){

24.  buckets[i]  =  new  Bucket();

25.  }

26.

27.  //3.遍历原始数组,确定每个桶的最大最小值

28.  for(int  i  =  0;  i  <  array.length;  i++){

29.  //确定数组元素所归属的桶下标

30.  int  index  =  ((array[i]  -  min)  *  (bucketNum-1)  /  d);

31.  if(buckets[index].min==null  ||  buckets[index].

min>array[i]){

32.  buckets[index].min  =  array[i];

33.  }

34.  if(buckets[index].max==null  ||  buckets[index].

max
35.  buckets[index].max  =  array[i];

36.  }

37.  }

38.

39.  //4.遍历桶,找到最大差值

40.  int  leftMax  =  buckets[0].max;

41.  int  maxDistance  =  0;

42.  for  (int  i=1;  i
43.  if  (buckets[i].min  ==  null)  {

44.  continue;

45.  }

46.  if  (buckets[i].min  -  leftMax  >  maxDistance)  {

47.  maxDistance  =  buckets[i].min  -  leftMax;

48.  }

49.  leftMax  =  buckets[i].max;

50.  }

51.

52.  return  maxDistance;

53.  }

54.

55.  /**

56.  *  桶

57.  */

58.  private  static  class  Bucket  {

59.  Integer  min;

60.  Integer  max;

61.  }

62.

63.  public  static  void  main(String[]  args)  {

64.  int[]  array  =  new  int[]  {2,6,3,4,5,10,9};

65.  System.out.println(getMaxSortedDistance(array));

66.  }

代码的前几步都比较直观,唯独第4步稍微有些不好理解:使用临时变量leftMax,在每一轮迭代时存储当前左侧桶的最大值。而两个桶之间的差值,则是buckets[i].minleftMax。

没错,这就是这道题目的最优解决方法。关于无序数组排序后最大差值的问题就介绍到这里,咱们下一节再见!