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. 【BZOJ 2118】 墨墨的等式(Dijkstra)

    BZOJ2118 墨墨的等式 题链:http://www.lydsy.com/JudgeOnline/problem.php?id=2118 Description 墨墨突然对等式很感兴趣,他正在研究 ...

  2. 集训第四周(高效算法设计)K题 (滑窗问题)

    UVA 11572 唯一的雪花 题意:给你从1到n的数组,要求求得其中的最长连续不重复子序列,经典的滑窗问题,方法是维护一个窗口,设置左框和右框,然后不断的进行维护和更新 方法一: #include& ...

  3. javaWeb学习之 Filter过滤器----https://www.cnblogs.com/xdp-gacl/p/3948353.html

    https://www.cnblogs.com/xdp-gacl/p/3948353.html

  4. HDU 5458 Stability

    Stability Time Limit: 2000ms Memory Limit: 102400KB This problem will be judged on HDU. Original ID: ...

  5. linux 命令练习 2018-08-27

    linux 命令练习 2018-08-27 uname  显示系统名字 [test@localhost ~]$ uname Linux uname -a   即列出linux的内核版本号 [test@ ...

  6. Thinkphp5.0 的请求方式

    Thinkphp5.0 的请求方式 方法一(使用框架提供的助手函数): public function index(){ $request = request(); dump($request); } ...

  7. 饭卡-HDU2546(01背包)

    http://acm.hdu.edu.cn/showproblem.php?pid=2546 饭卡 Time Limit: 5000/1000 MS (Java/Others)    Memory L ...

  8. Letter Combinations of a Phone Number(带for循环的DFS,组合问题,递归总结)

    Given a digit string, return all possible letter combinations that the number could represent. A map ...

  9. nodejs window下安装与配置淘宝镜像

    1,前往nodejs官网下载安装软件,地址:https://nodejs.org/en/ 2,点击下一步继续安装,安装完成,在命令输入:node -v,npm -v,查看版本,即是安装成功 3,随便在 ...

  10. Git回退---reset和revert

    今天学习了git回退的两个命令,现在总结一下: 1.git reset 如果想回退错误的提交C和D,只要把指针移到B上 git reset --hard a0fvf8 而这时候,远程仓库的指针还在D上 ...