https://leetcode.com/problems/maximum-gap/

Given an unsorted array, find the maximum difference between the successive elements in its sorted form.

Try to solve it in linear time/space.

Return 0 if the array contains less than 2 elements.

You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range.

解题思路:

看到题目,几个关键的信息。未排序、线性的时间和空间。

要计算相邻数字的最大gap,还要O(n)时间解决,能想到的就是先排序,然后遍历一遍返回最大值。于是,常见的线性排序,或者说《算法导论》里提到的,计数排序、基数排序和桶排序。

于是先写了下面的计数排序方法,不过是当作每个元素只出现一次对待的,因为题目算的是gap,多个相同的数字完全可以当作一个 数字看待。

public class Solution {
public int maximumGap(int[] num) {
if(num.length < 2) {
return 0;
} int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int i = 0; i < num.length; i++) {
min = Math.min(min, num[i]);
max = Math.max(max, num[i]);
} int[] numSort = new int[max - min + 1];
for(int i = 0; i < num.length; i++) {
numSort[num[i] - min] = num[i];
} int maxGap = 1;
int pre = numSort[0], cur = numSort[0];
for(int i = 1; i < numSort.length; i++) {
if(numSort[i] == 1) {
cur = numSort[i];
maxGap = Math.max(maxGap, cur - pre);
pre = cur;
}
} return maxGap;
}
}

结果遇到[2,99999999]这个例子的时候,内存溢出了,因为在创建那个numSort数组的时候,太大。

其实上面的计数排序就是一个鸽笼排序(Pigeonhole sort),即每个数字只会出现一次的计数排序。标准的计数排序中,记录每个数字出现次数的数组,大小也是必须为max - min + 1的,因为你没法知道在他们中间有多少数字。所以说,用计数排序,这个问题无法避免。只能另寻方法。

既然是因为两个数字相差太大,实际上并没有必要用那么大的数组,那么能不能以最小数字min为基础,用num[i]和min的差去除以平均的差,来确定当前数字可能在位置?

其实这就是桶排序。上面说了,计数排序最好是,所有数字在一个比较小的范围内。那么桶排序也要一个前提,就是所有数字最好尽量随机分散,不能太聚合。这样,他们才能被分散在每个桶里面,每个桶只有很少的数字,甚至0或1个,这个算法效率最高。

思路出来了,但是还有几个细节需要考虑。

1. 这个平均差(也就是桶的大小bucketSize),如何取?

对于一个有n个数字的数组,我们先取出最大数max,最小数min。因为总共有n - 1个gap,所以平均差为(max - min) / (n - 1)。注意,这个数字应该是一个精确值,也就是一个double。为什么?因为平均差可能是0.4,比如数字很多,但是相差却很小。为了避免平均差为0,这里必须取精确的double,或者对(max - min) / (n - 1)取ceil,也就是向上取整。注意不能取floor,否则平均差《1的时候,就变成0了。

2. 根据平均差,每个数字所在桶的下标如何计算?

每个数字的下标必须以min为底计算,(num[i] - min) / bucketSize,再对它取floor。这样,把每个数字向下归到了每个桶里。注意,在这里用ceil也是可以的,也就是向上归到每个桶里!

这样,min的下标为0,max的下标为n - 1。桶的大小和原数组一样,为n。

3. 最大gap如何计算?

因为有n个数字,n个桶。假设每个数字平均分在每个桶里。那么我们只需要逐一计算各个桶里唯一数字的差,返回最大值即可。

假设有一个或多个桶里的数字>1个,那么必然有一个桶里没数字,那么最大差一定在这个空桶周围发生,一定不在那个有多个数字的桶里。所以这时,我们只需要维护每个桶内数字的最大值和最小值。然后,用前一个桶的最大值和后一个桶的最小值比较,返回最大差即可。

回想第一种情况,在每个桶里有且仅有一个数字的情况,我们只要将最大值和最小值都设置为这个数值,然后用同样的方法计算,既可以返回最大差了。

代码如下。

public class Solution {
public int maximumGap(int[] num) {
if(num.length < 2) {
return 0;
}
if(num.length == 2) {
return Math.abs(num[0] - num[1]);
} int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int i = 0; i < num.length; i++) {
min = Math.min(min, num[i]);
max = Math.max(max, num[i]);
} double bucketSize = (double)(max - min) / (num.length - 1);
int[][] buckets = new int[num.length][2];
for(int i = 0; i < num.length; i++) {
int k = (int)Math.floor((double)(num[i] - min) / bucketSize);
if(buckets[k][0] == 0) {
buckets[k][0] = num[i];
} else {
buckets[k][0] = Math.min(buckets[k][0], num[i]);
}
if(buckets[k][1] == 0) {
buckets[k][1] = num[i];
} else {
buckets[k][1] = Math.max(buckets[k][1], num[i]);
}
} int maxGap = 0;
int preMax = 0;
for(int i = 0; i < buckets.length; i++) {
if(buckets[i][0] == 0 && buckets[i][1] == 0) {
continue;
}
if(preMax == 0) {
preMax = buckets[i][1];
continue;
}
maxGap = Math.max(maxGap, buckets[i][0] - preMax);
preMax = buckets[i][1];
} return maxGap;
}
}

bucketSize用ceil去做:

public class Solution {
public int maximumGap(int[] num) {
if(num.length < 2) {
return 0;
}
if(num.length == 2) {
return Math.abs(num[0] - num[1]);
} int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for(int i = 0; i < num.length; i++) {
min = Math.min(min, num[i]);
max = Math.max(max, num[i]);
} int bucketSize = (int)Math.ceil((double)(max - min)/(num.length - 1));
// 或者下面的方法也可以
// int bucketSize = (max - min)/num.length+ 1;
int[][] buckets = new int[num.length][2];
for(int i = 0; i < num.length; i++) {
int k = (num[i] - min) / bucketSize;
if(buckets[k][0] == 0) {
buckets[k][0] = num[i];
} else {
buckets[k][0] = Math.min(buckets[k][0], num[i]);
}
if(buckets[k][1] == 0) {
buckets[k][1] = num[i];
} else {
buckets[k][1] = Math.max(buckets[k][1], num[i]);
}
} int maxGap = 0;
int preMax = 0;
for(int i = 0; i < buckets.length; i++) {
if(buckets[i][0] == 0 && buckets[i][1] == 0) {
continue;
}
if(preMax == 0) {
preMax = buckets[i][1];
continue;
}
maxGap = Math.max(maxGap, buckets[i][0] - preMax);
preMax = buckets[i][1];
} return maxGap;
}
}

总结一下,这道题的考点主要在线性时间的几种算法,前面也总结了他们的优劣和适用情况。实际上计数排序常被作为基数排序的一个子方法来采用,也就是对某一位数字进行排序的时候,因为某一位数字的区间只有[0, 9],符合计数排序范围不大的前提。

在桶排序的时候,每个桶的内部排序常常使用插入排序,因为我们假定采用桶排序的数字,是比较分散的,这样落在每个桶里的数字就比较少。在数字比较少的时候,插入排序是比较快的。桶排序的平均时间复杂度为O(n),最坏也可能达到O(n^2),这取决于每个桶内的排序方法。但是,就像《算法导论》上面说的那样,桶排序的时间复杂度的数学预期是O(n)。

回到这道题目,我们看到因为要求的是max gap,所以无需对每个桶内部进行排序,只要维护每个桶各自的最大值和最小值即可。

最后,这道题桶排序的bucket size,bucket的大小,以及如何取bucket的下标,都是需要注意小心操作的。所谓talk is cheap, show me the code,即使知道思路,能写出bug-free的code也是不容易的。

最后给一个mcgill大学的课件,和本题很像。他去除了最大值和最小值,用抽屉原理实现推导,思路也是差不多。

http://cgm.cs.mcgill.ca/~godfried/teaching/dm-reading-assignments/Maximum-Gap-Problem.pdf

另外还有一个介绍桶排序的文章:http://hxraid.iteye.com/blog/647759

Maximum Gap 典型线性排序的更多相关文章

  1. 线性时间的排序算法--桶排序(以leetcode164. Maximum Gap为例讲解)

    前言 在比较排序的算法中,快速排序的性能最佳,时间复杂度是O(N*logN).因此,在使用比较排序时,时间复杂度的下限就是O(N*logN).而桶排序的时间复杂度是O(N+C),因为它的实现并不是基于 ...

  2. 【leetcode 桶排序】Maximum Gap

    1.题目 Given an unsorted array, find the maximum difference between the successive elements in its sor ...

  3. 由Maximum Gap,对话桶排序,基数排序和统计排序

    一些非比较排序 在LeetCode中有个题目叫Maximum Gap.是求一个非排序的正数数列中按顺序排列后的最大间隔.这个题用桶排序和基数排序都能够实现.以下说一下桶排序.基数排序和计数排序这三种非 ...

  4. leetcode[164] Maximum Gap

    梅西刚梅开二度,我也记一题. 在一个没排序的数组里,找出排序后的相邻数字的最大差值. 要求用线性时间和空间. 如果用nlgn的话,直接排序然后判断就可以了.so easy class Solution ...

  5. LeetCode 164. Maximum Gap[翻译]

    164. Maximum Gap 164. 最大间隔 Given an unsorted array, find the maximum difference between the successi ...

  6. 【LeetCode】164. Maximum Gap (2 solutions)

    Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...

  7. 【刷题-LeetCode】164 Maximum Gap

    Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...

  8. 【leetcode】Maximum Gap

    Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...

  9. [LintCode] Maximum Gap 求最大间距

    Given an unsorted array, find the maximum difference between the successive elements in its sorted f ...

随机推荐

  1. 优先队列重载运算符< 以及初始化列表

    优先队列定义 priority_queue<int, vector<int>, greater<int> >pq; 优先队列重载<运算符 在结构体中定义一个 ...

  2. Numpy第一课

    import numpy as np a = np.array([1,2,3]) print(a) # [1 2 3] a2 = np.array([[1, 2], [3, 4]]) print('a ...

  3. orcad中的快捷键

    在画原理图的时候,不能正常的将将要放下的器件与旁边的对其,一种解决办法是按F6,调出大的水平竖直线,在按F6,此线标消失. Ctrl+F8是全屏模式,关闭的方法暂时不知道,退出方式是点击按钮. F10 ...

  4. IDEA常用插件记录

    让我们来记录一下常用的IDEA插件:(从其他博客中取了许多图片,出处见图片水印) 1.JRebel for IntelliJ 热部署神器2.Free MyBatis plugin 实现dao层方法与x ...

  5. this关键字的由来及使用

    Student.java /* * 学生类 * * 起名字我们要求做到见名知意. * * 如果有局部变量名和成员变量名相同,在局部使用的时候,采用的是就近原则. * *我们有没有办法吧局部变量的nam ...

  6. english & utils & tools

    english & utils & tools https://openlanguage.com/ https://www.grammarly.com/blog/email-writi ...

  7. HDU 1564 找规律博弈

    题目大意是: 从n*n的方格角落的一个起点出发,每次移到上下左右一个未曾到达过的位置,谁不能走了谁就输了 想了好久都想不出,看了大神的题解 Orz了 果然博弈不是脑残的游戏啊... 这里从起点出发,将 ...

  8. RelativeLayout属性大全

  9. ZOJ 1298_Domino Effect

    题意: 多米诺骨牌效应:若干个关键牌相连,关键牌之间含有普通牌,关键牌倒下后其所在的行的普通牌全部倒下.求从推倒1号关键牌开始,最终倒下的牌的位置及时间. 分析: 最终倒下的牌的位置有两种情况,要么是 ...

  10. csu1365 Play with Chain

    很显然的splay,第一次用splay操作区间...我实在佩服这个targan大佬,居然搞出这么牛逼的平衡树,调了大概5个小时终于搞定了.. #include<cstdio> #inclu ...