一、题目描述:
给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。 如果数组元素个数小于 2,则返回 0。
示例 1:
输入: [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
示例 2:
输入: [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。
说明:
- 你可以假设数组中所有元素都是非负整数,且数值在 32 位有符号整数范围内。
- 请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。
二、思路分析
本题肯定是需要排序的,但要求时间复杂度O(n),那就只能用桶排序。关于桶排序,参考这篇博客
所以基本的思路就是,我们遍历一遍数组,找到整个数组的最大值maxv和最小值minv。这样就确定了元素的范围,接下来只要确定每个桶所能容纳的元素范围len,我们就能求出桶的个数count。有了这些桶之后,我们遍历数组,根据nums[i]确定元素应该放在哪个桶中,然后按顺序遍历每个桶,就可以得到排序数组了。
如何求出len呢?最直接的作法是让len = 1。但是这样的话,我们的count是元素范围(maxv - minv + 1)。题中说明数值在32位整数范围内,证明我们最坏情况可能会开个桶,空间开销极大
因此,在桶排序的基础上,我们要进行优化
我们可以这么来优化:题目要求我们求的是排序后相邻元素的最大差值,因此我们的目标是找到所有的排序数组相邻差值,然后取Max。那么我们可以每个桶中的任意元素之差都要小于最大差值,那么最大差值就必然出现在相邻的桶中。(也就是后一个桶中的最小值 - 前一个桶的最大值)这样的话,我们在遍历的时候,就只需要关心这个桶中存放的最大值和最小值即可。
那么现在问题就变成了我们如何设置桶的大小,来保证最大差值不存在于桶中呢?我们就需要来进行数学推导了:
1、假设每个元素都是均匀排列的,如: 0 5 10 15 20 25 30 35 40,此时最大间距是5。假设每个元素不是均匀排列,那么最大间距必定大于5。因此我们只需要让桶内部的间距小于平均间距,那么最大间距必然出现在桶间(设桶内间距为interval)
2、前面我们搞定了桶内间距,现在我们来思考一共需要几个桶。我们总共需要放n - 2个元素,因此我们其实只需要n - 2个桶,有两种情况,一种是存在空桶,那么桶间距必然大于桶内距离。另一种是每个元素分别在一个桶中,这种情况,就相当于已经排序了的数组,那么从前往后计算差值即可。
一个小细节
interval = (maxv - minv) / (n - 2) 不是整数应该上取整还是下去整
我们这里认定为上取整,令每个桶的范围是:[0, interval),那么每个桶的范围是:
- minv + 0 * interval ~ minv + 1 * interval - 1;
- minv + 1 * interval ~ minv + 2 * interval - 1;
- minv + 2 * interval ~ minv + 3 * interval - 1;
- minv + k * interval ~ minv + (k+1) * interval - 1;
这样我们将一个元素(nums[i]) 映射到桶的编号时,就可以用 ID = (nums[i] - minv) / intervals >> 0;
AC代码
/**
* @param {number[]} nums
* @return {number}
*/
class Bucket {
max = Number.MIN_SAFE_INTEGER
min = Number.MAX_SAFE_INTEGER
used = false
}
var maximumGap = function(nums) {
const n = nums.length;
if(n < 2) return 0;
let maxv = Math.max(...nums), minv = Math.min(...nums);
if(maxv == minv) return 0;
// 桶间间隔
const intervals = Math.ceil((maxv - minv) / (n - 2));
const buckets = Array.from({length: n - 1}, () => new Bucket());
// 将元素放置到对应的桶中
for(let i = 0; i < n; ++ i) {
if(nums[i] == minv || nums[i] == maxv) continue;
const ID = (nums[i] - minv) / intervals >> 0;
buckets[ID].used = true;
buckets[ID].max = Math.max(nums[i], buckets[ID].max);
buckets[ID].min = Math.min(nums[i], buckets[ID].min);
}
let maxGap = 0, preMax = minv;
for(let i = 0; i < n - 1; ++ i)
if(buckets[i].used) {
maxGap = Math.max(maxGap, buckets[i].min - preMax);
preMax = buckets[i].max;
}
maxGap = Math.max(maxGap, maxv - preMax);
return maxGap;
};
总结
本题在LeetCode中难度算是较高的,主要是其思维即具跳跃性,解题思路也不适用于其他题目,所以如果不理解的话也不用懊恼,掌握基本的算法和数据结构并熟练使用才是重中之重。
本文正在参与「掘金 3 月闯关活动」,点击查看活动详情