Maximum Gap 典型线性排序
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 典型线性排序的更多相关文章
- 线性时间的排序算法--桶排序(以leetcode164. Maximum Gap为例讲解)
前言 在比较排序的算法中,快速排序的性能最佳,时间复杂度是O(N*logN).因此,在使用比较排序时,时间复杂度的下限就是O(N*logN).而桶排序的时间复杂度是O(N+C),因为它的实现并不是基于 ...
- 【leetcode 桶排序】Maximum Gap
1.题目 Given an unsorted array, find the maximum difference between the successive elements in its sor ...
- 由Maximum Gap,对话桶排序,基数排序和统计排序
一些非比较排序 在LeetCode中有个题目叫Maximum Gap.是求一个非排序的正数数列中按顺序排列后的最大间隔.这个题用桶排序和基数排序都能够实现.以下说一下桶排序.基数排序和计数排序这三种非 ...
- leetcode[164] Maximum Gap
梅西刚梅开二度,我也记一题. 在一个没排序的数组里,找出排序后的相邻数字的最大差值. 要求用线性时间和空间. 如果用nlgn的话,直接排序然后判断就可以了.so easy class Solution ...
- LeetCode 164. Maximum Gap[翻译]
164. Maximum Gap 164. 最大间隔 Given an unsorted array, find the maximum difference between the successi ...
- 【LeetCode】164. Maximum Gap (2 solutions)
Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...
- 【刷题-LeetCode】164 Maximum Gap
Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...
- 【leetcode】Maximum Gap
Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...
- [LintCode] Maximum Gap 求最大间距
Given an unsorted array, find the maximum difference between the successive elements in its sorted f ...
随机推荐
- 优先队列重载运算符< 以及初始化列表
优先队列定义 priority_queue<int, vector<int>, greater<int> >pq; 优先队列重载<运算符 在结构体中定义一个 ...
- 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 ...
- orcad中的快捷键
在画原理图的时候,不能正常的将将要放下的器件与旁边的对其,一种解决办法是按F6,调出大的水平竖直线,在按F6,此线标消失. Ctrl+F8是全屏模式,关闭的方法暂时不知道,退出方式是点击按钮. F10 ...
- IDEA常用插件记录
让我们来记录一下常用的IDEA插件:(从其他博客中取了许多图片,出处见图片水印) 1.JRebel for IntelliJ 热部署神器2.Free MyBatis plugin 实现dao层方法与x ...
- this关键字的由来及使用
Student.java /* * 学生类 * * 起名字我们要求做到见名知意. * * 如果有局部变量名和成员变量名相同,在局部使用的时候,采用的是就近原则. * *我们有没有办法吧局部变量的nam ...
- english & utils & tools
english & utils & tools https://openlanguage.com/ https://www.grammarly.com/blog/email-writi ...
- HDU 1564 找规律博弈
题目大意是: 从n*n的方格角落的一个起点出发,每次移到上下左右一个未曾到达过的位置,谁不能走了谁就输了 想了好久都想不出,看了大神的题解 Orz了 果然博弈不是脑残的游戏啊... 这里从起点出发,将 ...
- RelativeLayout属性大全
- ZOJ 1298_Domino Effect
题意: 多米诺骨牌效应:若干个关键牌相连,关键牌之间含有普通牌,关键牌倒下后其所在的行的普通牌全部倒下.求从推倒1号关键牌开始,最终倒下的牌的位置及时间. 分析: 最终倒下的牌的位置有两种情况,要么是 ...
- csu1365 Play with Chain
很显然的splay,第一次用splay操作区间...我实在佩服这个targan大佬,居然搞出这么牛逼的平衡树,调了大概5个小时终于搞定了.. #include<cstdio> #inclu ...